mirror of https://github.com/fail2ban/fail2ban
Merge remote-tracking branch 0.10 into _0.10/fix-datedetector-grave-fix-v2
commit
40cbe96352
23
ChangeLog
23
ChangeLog
|
@ -37,6 +37,17 @@ TODO: implementing of options resp. other tasks from PR #1346
|
||||||
* Pyinotify-backend: stability fix for sporadically errors in multi-threaded
|
* Pyinotify-backend: stability fix for sporadically errors in multi-threaded
|
||||||
environment (without lock)
|
environment (without lock)
|
||||||
* Fixed sporadically error in testCymruInfoNxdomain, because of unsorted values
|
* Fixed sporadically error in testCymruInfoNxdomain, because of unsorted values
|
||||||
|
* Misleading errors logged from ignorecommand in success case on retcode 1 (gh-1194)
|
||||||
|
* fail2ban.service - systemd service updated (gh-1618):
|
||||||
|
- starting service in normal mode (without forking)
|
||||||
|
- does not restart if service exited normally (exit-code 0, e.g. stopped via fail2ban-client)
|
||||||
|
- does not restart if service can not start (exit-code 255, e.g. wrong configuration, etc.)
|
||||||
|
- service can be additionally started/stopped with commands (fail2ban-client, fail2ban-server)
|
||||||
|
- automatically creates `/var/run/fail2ban` directory before start fail2ban
|
||||||
|
(systems with virtual resp. memory-based FS for `/var/run`), see gh-1531
|
||||||
|
- if fail2ban running as systemd-service, for logging to the systemd-journal,
|
||||||
|
the `logtarget` could be set to STDOUT
|
||||||
|
- value `logtarget` for system targets allowed also in lowercase (stdout, stderr, syslog, etc.)
|
||||||
* Fixed UTC/GMT named time zone, using `%Z` and `%z` patterns
|
* Fixed UTC/GMT named time zone, using `%Z` and `%z` patterns
|
||||||
(special case with 0 zone offset, see gh-1575)
|
(special case with 0 zone offset, see gh-1575)
|
||||||
* `filter.d/freeswitch.conf`
|
* `filter.d/freeswitch.conf`
|
||||||
|
@ -68,6 +79,8 @@ TODO: implementing of options resp. other tasks from PR #1346
|
||||||
banned in this jail, if option `--unban` specified
|
banned in this jail, if option `--unban` specified
|
||||||
- `unban --all` - unbans all IP addresses (in all jails and database)
|
- `unban --all` - unbans all IP addresses (in all jails and database)
|
||||||
- `unban <IP> ... <IP>` - unbans \<IP\> (in all jails and database) (see gh-1388)
|
- `unban <IP> ... <IP>` - unbans \<IP\> (in all jails and database) (see gh-1388)
|
||||||
|
- introduced new option `-t` or `--test` to test configuration resp. start server only
|
||||||
|
if configuration is clean (fails by wrong configured jails if option `-t` specified)
|
||||||
* New command action parameter `actionrepair` - command executed in order to restore
|
* New command action parameter `actionrepair` - command executed in order to restore
|
||||||
sane environment in error case of `actioncheck`.
|
sane environment in error case of `actioncheck`.
|
||||||
|
|
||||||
|
@ -132,6 +145,10 @@ fail2ban-client set loglevel INFO
|
||||||
- new replacement for `<ADDR>` in opposition to `<HOST>`, for separate
|
- new replacement for `<ADDR>` in opposition to `<HOST>`, for separate
|
||||||
usage of 2 address groups only (regardless of `usedns`), `ip4` and `ip6`
|
usage of 2 address groups only (regardless of `usedns`), `ip4` and `ip6`
|
||||||
together, without host (dns)
|
together, without host (dns)
|
||||||
|
* Misconfigured jails don't prevent fail2ban from starting, server starts
|
||||||
|
nevertheless, as long as one jail was successful configured (gh-1619)
|
||||||
|
Message about wrong jail configuration logged in client log (stdout, systemd
|
||||||
|
journal etc.) and in server log with error level
|
||||||
* More precise date template handling (WARNING: theoretically possible incompatibilities):
|
* More precise date template handling (WARNING: theoretically possible incompatibilities):
|
||||||
- datedetector rewritten more strict as earlier;
|
- datedetector rewritten more strict as earlier;
|
||||||
- default templates can be specified exacter using prefix/suffix syntax (via `datepattern`);
|
- default templates can be specified exacter using prefix/suffix syntax (via `datepattern`);
|
||||||
|
@ -185,6 +202,12 @@ releases.
|
||||||
- Allow for having no trailing space after 'failed:' (gh-1497)
|
- Allow for having no trailing space after 'failed:' (gh-1497)
|
||||||
* `filter.d/vsftpd.conf`
|
* `filter.d/vsftpd.conf`
|
||||||
- Optional reason part in message after FAIL LOGIN (gh-1543)
|
- Optional reason part in message after FAIL LOGIN (gh-1543)
|
||||||
|
* `filter.d/sendmail-reject.conf`
|
||||||
|
- removed mandatory double space (if dns-host available, gh-1579)
|
||||||
|
* filter.d/sshd.conf
|
||||||
|
- recognized "Failed publickey for" (gh-1477);
|
||||||
|
- optimized failregex to match all of "Failed any-method for ... from <HOST>" (gh-1479)
|
||||||
|
- eliminated possible complex injections (on user-name resp. auth-info, see gh-1479)
|
||||||
|
|
||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
|
|
2
FILTERS
2
FILTERS
|
@ -227,7 +227,7 @@ Regular expressions (failregex, ignoreregex) assume that the date/time has been
|
||||||
removed from the log line (this is just how fail2ban works internally ATM).
|
removed from the log line (this is just how fail2ban works internally ATM).
|
||||||
|
|
||||||
If the format is like '<date...> error 1.2.3.4 is evil' then you need to match
|
If the format is like '<date...> error 1.2.3.4 is evil' then you need to match
|
||||||
the < at the start so regex should be similar to '^<> <HOST> is evil$' using
|
the <> at the start so regex should be similar to '^<> error <HOST> is evil$' using
|
||||||
<HOST> where the IP/domain name appears in the log line.
|
<HOST> where the IP/domain name appears in the log line.
|
||||||
|
|
||||||
The following general rules apply to regular expressions:
|
The following general rules apply to regular expressions:
|
||||||
|
|
|
@ -28,6 +28,10 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
[INCLUDES]
|
||||||
|
|
||||||
|
before = helpers-common.conf
|
||||||
|
|
||||||
[Definition]
|
[Definition]
|
||||||
|
|
||||||
# Option: actionstart
|
# Option: actionstart
|
||||||
|
@ -54,10 +58,16 @@ actioncheck =
|
||||||
# Tags: See jail.conf(5) man page
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionban = oifs=${IFS}; IFS=.;SEP_IP=( <ip> ); set -- ${SEP_IP}; ADDRESSES=$(dig +short -t txt -q $4.$3.$2.$1.abuse-contacts.abusix.org); IFS=${oifs}
|
actionban = oifs=${IFS};
|
||||||
IP=<ip>
|
IFS=.; SEP_IP=( <ip> ); set -- ${SEP_IP}; ADDRESSES=$(dig +short -t txt -q $4.$3.$2.$1.abuse-contacts.abusix.org);
|
||||||
|
IFS=,; ADDRESSES=$(echo $ADDRESSES)
|
||||||
|
IFS=${oifs}
|
||||||
|
IP=<ip>
|
||||||
if [ ! -z "$ADDRESSES" ]; then
|
if [ ! -z "$ADDRESSES" ]; then
|
||||||
(printf %%b "<message>\n"; date '+Note: Local timezone is %%z (%%Z)'; grep -E '(^|[^0-9])<ip>([^0-9]|$)' <logpath>) | <mailcmd> "Abuse from <ip>" <mailargs> ${ADDRESSES//,/\" \"}
|
( printf %%b "<message>\n"; date '+Note: Local timezone is %%z (%%Z)';
|
||||||
|
printf %%b "\nLines containing failures of <ip> (max <grepmax>)\n";
|
||||||
|
%(_grep_logs)s;
|
||||||
|
) | <mailcmd> "Abuse from <ip>" <mailargs> $ADDRESSES
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Option: actionunban
|
# Option: actionunban
|
||||||
|
@ -92,3 +102,7 @@ mailcmd = mail -s
|
||||||
#
|
#
|
||||||
mailargs =
|
mailargs =
|
||||||
|
|
||||||
|
# Number of log lines to include in the email
|
||||||
|
#
|
||||||
|
#grepmax = 1000
|
||||||
|
#grepopts = -m <grepmax>
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
[DEFAULT]
|
||||||
|
|
||||||
|
# Usage:
|
||||||
|
# _grep_logs_args = 'test'
|
||||||
|
# (printf %%b "Log-excerpt contains 'test':\n"; %(_grep_logs)s; printf %%b "Log-excerpt contains 'test':\n") | mail ...
|
||||||
|
#
|
||||||
|
_grep_logs = logpath="<logpath>"; grep <grepopts> -E %(_grep_logs_args)s $logpath | <greplimit>
|
||||||
|
_grep_logs_args = '(^|[^0-9])<ip>([^0-9]|$)'
|
||||||
|
|
||||||
|
[Init]
|
||||||
|
greplimit = tail -n <grepmax>
|
||||||
|
grepmax = 1000
|
||||||
|
grepopts = -m <grepmax>
|
|
@ -7,6 +7,7 @@
|
||||||
[INCLUDES]
|
[INCLUDES]
|
||||||
|
|
||||||
before = mail-whois-common.conf
|
before = mail-whois-common.conf
|
||||||
|
helpers-common.conf
|
||||||
|
|
||||||
[Definition]
|
[Definition]
|
||||||
|
|
||||||
|
@ -17,7 +18,7 @@ before = mail-whois-common.conf
|
||||||
actionstart = printf %%b "Hi,\n
|
actionstart = printf %%b "Hi,\n
|
||||||
The jail <name> has been started successfully.\n
|
The jail <name> has been started successfully.\n
|
||||||
Regards,\n
|
Regards,\n
|
||||||
Fail2Ban"|mail -s "[Fail2Ban] <name>: started on `uname -n`" <dest>
|
Fail2Ban" | <mailcmd> -s "[Fail2Ban] <name>: started on `uname -n`" <dest>
|
||||||
|
|
||||||
# Option: actionstop
|
# Option: actionstop
|
||||||
# Notes.: command executed once at the end of Fail2Ban
|
# Notes.: command executed once at the end of Fail2Ban
|
||||||
|
@ -26,7 +27,7 @@ actionstart = printf %%b "Hi,\n
|
||||||
actionstop = printf %%b "Hi,\n
|
actionstop = printf %%b "Hi,\n
|
||||||
The jail <name> has been stopped.\n
|
The jail <name> has been stopped.\n
|
||||||
Regards,\n
|
Regards,\n
|
||||||
Fail2Ban"|mail -s "[Fail2Ban] <name>: stopped on `uname -n`" <dest>
|
Fail2Ban" | <mailcmd> -s "[Fail2Ban] <name>: stopped on `uname -n`" <dest>
|
||||||
|
|
||||||
# Option: actioncheck
|
# Option: actioncheck
|
||||||
# Notes.: command executed once before each actionban command
|
# Notes.: command executed once before each actionban command
|
||||||
|
@ -40,15 +41,18 @@ actioncheck =
|
||||||
# Tags: See jail.conf(5) man page
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionban = printf %%b "Hi,\n
|
|
||||||
|
_ban_mail_content = ( printf %%b "Hi,\n
|
||||||
The IP <ip> has just been banned by Fail2Ban after
|
The IP <ip> has just been banned by Fail2Ban after
|
||||||
<failures> attempts against <name>.\n\n
|
<failures> attempts against <name>.\n\n
|
||||||
Here is more information about <ip> :\n
|
Here is more information about <ip> :\n"
|
||||||
`%(_whois_command)s`\n\n
|
%(_whois_command)s;
|
||||||
Lines containing IP:<ip> in <logpath>\n
|
printf %%b "\nLines containing failures of <ip> (max <grepmax>)\n";
|
||||||
`grep -E <grepopts> '(^|[^0-9])<ip>([^0-9]|$)' <logpath>`\n\n
|
%(_grep_logs)s;
|
||||||
|
printf %%b "\n
|
||||||
Regards,\n
|
Regards,\n
|
||||||
Fail2Ban"|mail -s "[Fail2Ban] <name>: banned <ip> from `uname -n`" <dest>
|
Fail2Ban" )
|
||||||
|
actionban = %(_ban_mail_content)s | <mailcmd> "[Fail2Ban] <name>: banned <ip> from `uname -n`" <dest>
|
||||||
|
|
||||||
# Option: actionunban
|
# Option: actionunban
|
||||||
# Notes.: command executed when unbanning an IP. Take care that the
|
# Notes.: command executed when unbanning an IP. Take care that the
|
||||||
|
@ -60,6 +64,12 @@ actionunban =
|
||||||
|
|
||||||
[Init]
|
[Init]
|
||||||
|
|
||||||
|
# Option: mailcmd
|
||||||
|
# Notes.: Your system mail command. Is passed 2 args: subject and recipient
|
||||||
|
# Values: CMD
|
||||||
|
#
|
||||||
|
mailcmd = mail -s
|
||||||
|
|
||||||
# Default name of the chain
|
# Default name of the chain
|
||||||
#
|
#
|
||||||
name = default
|
name = default
|
||||||
|
@ -74,4 +84,5 @@ logpath = /dev/null
|
||||||
|
|
||||||
# Number of log lines to include in the email
|
# Number of log lines to include in the email
|
||||||
#
|
#
|
||||||
grepopts = -m 1000
|
#grepmax = 1000
|
||||||
|
#grepopts = -m <grepmax>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
[INCLUDES]
|
[INCLUDES]
|
||||||
|
|
||||||
before = sendmail-common.conf
|
before = sendmail-common.conf
|
||||||
|
helpers-common.conf
|
||||||
|
|
||||||
[Definition]
|
[Definition]
|
||||||
|
|
||||||
|
@ -19,7 +20,7 @@ before = sendmail-common.conf
|
||||||
# Tags: See jail.conf(5) man page
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
|
actionban = ( printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
|
||||||
Date: `LC_ALL=C date +"%%a, %%d %%h %%Y %%T %%z"`
|
Date: `LC_ALL=C date +"%%a, %%d %%h %%Y %%T %%z"`
|
||||||
From: <sendername> <<sender>>
|
From: <sendername> <<sender>>
|
||||||
To: <dest>\n
|
To: <dest>\n
|
||||||
|
@ -33,10 +34,11 @@ actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
|
||||||
Country:`geoiplookup -f /usr/share/GeoIP/GeoIP.dat "<ip>" | cut -d':' -f2-`
|
Country:`geoiplookup -f /usr/share/GeoIP/GeoIP.dat "<ip>" | cut -d':' -f2-`
|
||||||
AS:`geoiplookup -f /usr/share/GeoIP/GeoIPASNum.dat "<ip>" | cut -d':' -f2-`
|
AS:`geoiplookup -f /usr/share/GeoIP/GeoIPASNum.dat "<ip>" | cut -d':' -f2-`
|
||||||
hostname: `host -t A <ip> 2>&1`\n\n
|
hostname: `host -t A <ip> 2>&1`\n\n
|
||||||
Lines containing IP:<ip> in <logpath>\n
|
Lines containing failures of <ip>\n";
|
||||||
`grep -E <grepopts> '(^|[^0-9])<ip>([^0-9]|$)' <logpath>`\n\n
|
%(_grep_logs)s;
|
||||||
|
printf %%b "\n
|
||||||
Regards,\n
|
Regards,\n
|
||||||
Fail2Ban" | /usr/sbin/sendmail -f <sender> <dest>
|
Fail2Ban" ) | /usr/sbin/sendmail -f <sender> <dest>
|
||||||
|
|
||||||
[Init]
|
[Init]
|
||||||
|
|
||||||
|
@ -50,4 +52,5 @@ logpath = /dev/null
|
||||||
|
|
||||||
# Number of log lines to include in the email
|
# Number of log lines to include in the email
|
||||||
#
|
#
|
||||||
grepopts = -m 1000
|
#grepmax = 1000
|
||||||
|
#grepopts = -m <grepmax>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
[INCLUDES]
|
[INCLUDES]
|
||||||
|
|
||||||
before = sendmail-common.conf
|
before = sendmail-common.conf
|
||||||
|
helpers-common.conf
|
||||||
|
|
||||||
[Definition]
|
[Definition]
|
||||||
|
|
||||||
|
@ -16,7 +17,7 @@ before = sendmail-common.conf
|
||||||
# Tags: See jail.conf(5) man page
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
|
actionban = ( printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
|
||||||
Date: `LC_ALL=C date +"%%a, %%d %%h %%Y %%T %%z"`
|
Date: `LC_ALL=C date +"%%a, %%d %%h %%Y %%T %%z"`
|
||||||
From: <sendername> <<sender>>
|
From: <sendername> <<sender>>
|
||||||
To: <dest>\n
|
To: <dest>\n
|
||||||
|
@ -25,10 +26,11 @@ actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
|
||||||
<failures> attempts against <name>.\n\n
|
<failures> attempts against <name>.\n\n
|
||||||
Here is more information about <ip> :\n
|
Here is more information about <ip> :\n
|
||||||
`/usr/bin/whois <ip> || echo missing whois program`\n\n
|
`/usr/bin/whois <ip> || echo missing whois program`\n\n
|
||||||
Lines containing IP:<ip> in <logpath>\n
|
Lines containing failures of <ip>\n";
|
||||||
`grep -E <grepopts> '(^|[^0-9])<ip>([^0-9]|$)' <logpath>`\n\n
|
%(_grep_logs)s;
|
||||||
|
printf %%b "\n
|
||||||
Regards,\n
|
Regards,\n
|
||||||
Fail2Ban" | /usr/sbin/sendmail -f <sender> <dest>
|
Fail2Ban" ) | /usr/sbin/sendmail -f <sender> <dest>
|
||||||
|
|
||||||
[Init]
|
[Init]
|
||||||
|
|
||||||
|
@ -42,4 +44,5 @@ logpath = /dev/null
|
||||||
|
|
||||||
# Number of log lines to include in the email
|
# Number of log lines to include in the email
|
||||||
#
|
#
|
||||||
grepopts = -m 1000
|
#grepmax = 1000
|
||||||
|
#grepopts = -m <grepmax>
|
||||||
|
|
|
@ -23,7 +23,7 @@ _daemon = (?:(sm-(mta|acceptingconnections)|sendmail))
|
||||||
|
|
||||||
failregex = ^%(__prefix_line)s\w{14}: ruleset=check_rcpt, arg1=(?P<email><\S+@\S+>), relay=(\S+ )?\[<HOST>\]( \(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))$
|
failregex = ^%(__prefix_line)s\w{14}: ruleset=check_rcpt, arg1=(?P<email><\S+@\S+>), relay=(\S+ )?\[<HOST>\]( \(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))$
|
||||||
^%(__prefix_line)sruleset=check_relay, arg1=(?P<dom>\S+), arg2=<HOST>, relay=((?P=dom) )?\[(\d+\.){3}\d+\]( \(may be forged\))?, reject=421 4\.3\.2 (Connection rate limit exceeded\.|Too many open connections\.)$
|
^%(__prefix_line)sruleset=check_relay, arg1=(?P<dom>\S+), arg2=<HOST>, relay=((?P=dom) )?\[(\d+\.){3}\d+\]( \(may be forged\))?, reject=421 4\.3\.2 (Connection rate limit exceeded\.|Too many open connections\.)$
|
||||||
^%(__prefix_line)s\w{14}: rejecting commands from (\S+ )?\[<HOST>\] due to pre-greeting traffic after \d+ seconds$
|
^%(__prefix_line)s\w{14}: rejecting commands from (\S* )?\[<HOST>\] due to pre-greeting traffic after \d+ seconds$
|
||||||
^%(__prefix_line)s\w{14}: (\S+ )?\[<HOST>\]: ((?i)expn|vrfy) \S+ \[rejected\]$
|
^%(__prefix_line)s\w{14}: (\S+ )?\[<HOST>\]: ((?i)expn|vrfy) \S+ \[rejected\]$
|
||||||
^(?P<__prefix>%(__prefix_line)s\w+: )<[^@]+@[^>]+>\.\.\. No such user here<SKIPLINES>(?P=__prefix)from=<[^@]+@[^>]+>, size=\d+, class=\d+, nrcpts=\d+, bodytype=\w+, proto=E?SMTP, daemon=MTA, relay=\S+ \[<HOST>\]$
|
^(?P<__prefix>%(__prefix_line)s\w+: )<[^@]+@[^>]+>\.\.\. No such user here<SKIPLINES>(?P=__prefix)from=<[^@]+@[^>]+>, size=\d+, class=\d+, nrcpts=\d+, bodytype=\w+, proto=E?SMTP, daemon=MTA, relay=\S+ \[<HOST>\]$
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ _daemon = sshd
|
||||||
|
|
||||||
failregex = ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|error|failed) for .* from <HOST>( via \S+)?\s*$
|
failregex = ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|error|failed) for .* from <HOST>( via \S+)?\s*$
|
||||||
^%(__prefix_line)s(?:error: PAM: )?User not known to the underlying authentication module for .* from <HOST>\s*$
|
^%(__prefix_line)s(?:error: PAM: )?User not known to the underlying authentication module for .* from <HOST>\s*$
|
||||||
^%(__prefix_line)sFailed \S+ for .*? from <HOST>(?: port \d*)?(?: ssh\d*)?(: (ruser .*|(\S+ ID \S+ \(serial \d+\) CA )?\S+ %(__md5hex)s(, client user ".*", client host ".*")?))?\s*$
|
^%(__prefix_line)sFailed \S+ for (?P<cond_inv>invalid user )?(?P<user>(?P<cond_user>\S+)|(?(cond_inv)(?:(?! from ).)*?|[^:]+)) from <HOST>(?: port \d+)?(?: ssh\d*)?(?(cond_user):|(?:(?:(?! from ).)*)$)
|
||||||
^%(__prefix_line)sROOT LOGIN REFUSED.* FROM <HOST>\s*$
|
^%(__prefix_line)sROOT LOGIN REFUSED.* FROM <HOST>\s*$
|
||||||
^%(__prefix_line)s[iI](?:llegal|nvalid) user .* from <HOST>\s*$
|
^%(__prefix_line)s[iI](?:llegal|nvalid) user .* from <HOST>\s*$
|
||||||
^%(__prefix_line)sUser .+ from <HOST> not allowed because not listed in AllowUsers\s*$
|
^%(__prefix_line)sUser .+ from <HOST> not allowed because not listed in AllowUsers\s*$
|
||||||
|
|
|
@ -29,7 +29,7 @@ import re
|
||||||
import sys
|
import sys
|
||||||
from ..helpers import getLogger
|
from ..helpers import getLogger
|
||||||
|
|
||||||
if sys.version_info >= (3,2): # pragma: no cover
|
if sys.version_info >= (3,2):
|
||||||
|
|
||||||
# SafeConfigParser deprecated from Python 3.2 (renamed to ConfigParser)
|
# SafeConfigParser deprecated from Python 3.2 (renamed to ConfigParser)
|
||||||
from configparser import ConfigParser as SafeConfigParser, \
|
from configparser import ConfigParser as SafeConfigParser, \
|
||||||
|
|
|
@ -28,13 +28,25 @@ import glob
|
||||||
import os
|
import os
|
||||||
from ConfigParser import NoOptionError, NoSectionError
|
from ConfigParser import NoOptionError, NoSectionError
|
||||||
|
|
||||||
from .configparserinc import SafeConfigParserWithIncludes, logLevel
|
from .configparserinc import sys, SafeConfigParserWithIncludes, logLevel
|
||||||
from ..helpers import getLogger
|
from ..helpers import getLogger
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = getLogger(__name__)
|
logSys = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# if sys.version_info >= (3,5):
|
||||||
|
# def _merge_dicts(x, y):
|
||||||
|
# return {**x, **y}
|
||||||
|
# else:
|
||||||
|
def _merge_dicts(x, y):
|
||||||
|
r = x
|
||||||
|
if y:
|
||||||
|
r = x.copy()
|
||||||
|
r.update(y)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
class ConfigReader():
|
class ConfigReader():
|
||||||
"""Generic config reader class.
|
"""Generic config reader class.
|
||||||
|
|
||||||
|
@ -127,9 +139,9 @@ class ConfigReader():
|
||||||
return self._cfg.options(*args)
|
return self._cfg.options(*args)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get(self, sec, opt):
|
def get(self, sec, opt, raw=False, vars={}):
|
||||||
if self._cfg is not None:
|
if self._cfg is not None:
|
||||||
return self._cfg.get(sec, opt)
|
return self._cfg.get(sec, opt, raw=raw, vars=vars)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def getOptions(self, *args, **kwargs):
|
def getOptions(self, *args, **kwargs):
|
||||||
|
@ -210,6 +222,8 @@ class ConfigReaderUnshared(SafeConfigParserWithIncludes):
|
||||||
|
|
||||||
def getOptions(self, sec, options, pOptions=None, shouldExist=False):
|
def getOptions(self, sec, options, pOptions=None, shouldExist=False):
|
||||||
values = dict()
|
values = dict()
|
||||||
|
if pOptions is None:
|
||||||
|
pOptions = {}
|
||||||
for optname in options:
|
for optname in options:
|
||||||
if isinstance(options, (list,tuple)):
|
if isinstance(options, (list,tuple)):
|
||||||
if len(optname) > 2:
|
if len(optname) > 2:
|
||||||
|
@ -218,15 +232,15 @@ class ConfigReaderUnshared(SafeConfigParserWithIncludes):
|
||||||
(opttype, optname), optvalue = optname, None
|
(opttype, optname), optvalue = optname, None
|
||||||
else:
|
else:
|
||||||
opttype, optvalue = options[optname]
|
opttype, optvalue = options[optname]
|
||||||
|
if optname in pOptions:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
if opttype == "bool":
|
if opttype == "bool":
|
||||||
v = self.getboolean(sec, optname)
|
v = self.getboolean(sec, optname)
|
||||||
elif opttype == "int":
|
elif opttype == "int":
|
||||||
v = self.getint(sec, optname)
|
v = self.getint(sec, optname)
|
||||||
else:
|
else:
|
||||||
v = self.get(sec, optname)
|
v = self.get(sec, optname, vars=pOptions)
|
||||||
if not pOptions is None and optname in pOptions:
|
|
||||||
continue
|
|
||||||
values[optname] = v
|
values[optname] = v
|
||||||
except NoSectionError as e:
|
except NoSectionError as e:
|
||||||
if shouldExist:
|
if shouldExist:
|
||||||
|
@ -289,6 +303,12 @@ class DefinitionInitConfigReader(ConfigReader):
|
||||||
return SafeConfigParserWithIncludes.read(self._cfg, self._file)
|
return SafeConfigParserWithIncludes.read(self._cfg, self._file)
|
||||||
|
|
||||||
def getOptions(self, pOpts):
|
def getOptions(self, pOpts):
|
||||||
|
# overwrite static definition options with init values, supplied as
|
||||||
|
# direct parameters from jail-config via action[xtra1="...", xtra2=...]:
|
||||||
|
if self._initOpts:
|
||||||
|
if not pOpts:
|
||||||
|
pOpts = dict()
|
||||||
|
pOpts = _merge_dicts(pOpts, self._initOpts)
|
||||||
self._opts = ConfigReader.getOptions(
|
self._opts = ConfigReader.getOptions(
|
||||||
self, "Definition", self._configOpts, pOpts)
|
self, "Definition", self._configOpts, pOpts)
|
||||||
|
|
||||||
|
|
|
@ -72,9 +72,9 @@ class Configurator:
|
||||||
def getEarlyOptions(self):
|
def getEarlyOptions(self):
|
||||||
return self.__fail2ban.getEarlyOptions()
|
return self.__fail2ban.getEarlyOptions()
|
||||||
|
|
||||||
def getOptions(self, jail=None, updateMainOpt=None):
|
def getOptions(self, jail=None, updateMainOpt=None, ignoreWrong=True):
|
||||||
self.__fail2ban.getOptions(updateMainOpt)
|
self.__fail2ban.getOptions(updateMainOpt)
|
||||||
return self.__jails.getOptions(jail)
|
return self.__jails.getOptions(jail, ignoreWrong=ignoreWrong)
|
||||||
|
|
||||||
def convertToProtocol(self):
|
def convertToProtocol(self):
|
||||||
self.__streams["general"] = self.__fail2ban.convert()
|
self.__streams["general"] = self.__fail2ban.convert()
|
||||||
|
|
|
@ -125,7 +125,7 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
||||||
if client:
|
if client:
|
||||||
try :
|
try :
|
||||||
client.close()
|
client.close()
|
||||||
except Exception as e:
|
except Exception as e: # pragma: no cover
|
||||||
if showRet or self._conf["verbose"] > 1:
|
if showRet or self._conf["verbose"] > 1:
|
||||||
logSys.debug(e)
|
logSys.debug(e)
|
||||||
if showRet or c[0] == 'echo':
|
if showRet or c[0] == 'echo':
|
||||||
|
|
|
@ -47,6 +47,7 @@ class Fail2banCmdLine():
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._argv = self._args = None
|
self._argv = self._args = None
|
||||||
self._configurator = None
|
self._configurator = None
|
||||||
|
self.cleanConfOnly = False
|
||||||
self.resetConf()
|
self.resetConf()
|
||||||
|
|
||||||
def resetConf(self):
|
def resetConf(self):
|
||||||
|
@ -101,6 +102,7 @@ class Fail2banCmdLine():
|
||||||
output(" --logtarget <FILE>|STDOUT|STDERR|SYSLOG")
|
output(" --logtarget <FILE>|STDOUT|STDERR|SYSLOG")
|
||||||
output(" --syslogsocket auto|<FILE>")
|
output(" --syslogsocket auto|<FILE>")
|
||||||
output(" -d dump configuration. For debugging")
|
output(" -d dump configuration. For debugging")
|
||||||
|
output(" -t, --test test configuration (can be also specified with start parameters)")
|
||||||
output(" -i interactive mode")
|
output(" -i interactive mode")
|
||||||
output(" -v increase verbosity")
|
output(" -v increase verbosity")
|
||||||
output(" -q decrease verbosity")
|
output(" -q decrease verbosity")
|
||||||
|
@ -136,6 +138,9 @@ class Fail2banCmdLine():
|
||||||
self._conf[ o[2:] ] = opt[1]
|
self._conf[ o[2:] ] = opt[1]
|
||||||
elif o == "-d":
|
elif o == "-d":
|
||||||
self._conf["dump"] = True
|
self._conf["dump"] = True
|
||||||
|
elif o == "-t" or o == "--test":
|
||||||
|
self.cleanConfOnly = True
|
||||||
|
self._conf["test"] = True
|
||||||
elif o == "-v":
|
elif o == "-v":
|
||||||
self._conf["verbose"] += 1
|
self._conf["verbose"] += 1
|
||||||
elif o == "-q":
|
elif o == "-q":
|
||||||
|
@ -173,8 +178,8 @@ class Fail2banCmdLine():
|
||||||
|
|
||||||
# Reads the command line options.
|
# Reads the command line options.
|
||||||
try:
|
try:
|
||||||
cmdOpts = 'hc:s:p:xfbdviqV'
|
cmdOpts = 'hc:s:p:xfbdtviqV'
|
||||||
cmdLongOpts = ['loglevel=', 'logtarget=', 'syslogsocket=', 'async', 'timeout=', 'help', 'version']
|
cmdLongOpts = ['loglevel=', 'logtarget=', 'syslogsocket=', 'test', 'async', 'timeout=', 'help', 'version']
|
||||||
optList, self._args = getopt.getopt(self._argv[1:], cmdOpts, cmdLongOpts)
|
optList, self._args = getopt.getopt(self._argv[1:], cmdOpts, cmdLongOpts)
|
||||||
except getopt.GetoptError:
|
except getopt.GetoptError:
|
||||||
self.dispUsage()
|
self.dispUsage()
|
||||||
|
@ -225,13 +230,30 @@ class Fail2banCmdLine():
|
||||||
logSys.info("Using pid file %s, [%s] logging to %s",
|
logSys.info("Using pid file %s, [%s] logging to %s",
|
||||||
self._conf["pidfile"], logging.getLevelName(llev), self._conf["logtarget"])
|
self._conf["pidfile"], logging.getLevelName(llev), self._conf["logtarget"])
|
||||||
|
|
||||||
|
readcfg = True
|
||||||
if self._conf.get("dump", False):
|
if self._conf.get("dump", False):
|
||||||
ret, stream = self.readConfig()
|
if readcfg:
|
||||||
|
ret, stream = self.readConfig()
|
||||||
|
readcfg = False
|
||||||
self.dumpConfig(stream)
|
self.dumpConfig(stream)
|
||||||
return ret
|
if not self._conf.get("test", False):
|
||||||
|
return ret
|
||||||
|
|
||||||
|
if self._conf.get("test", False):
|
||||||
|
if readcfg:
|
||||||
|
readcfg = False
|
||||||
|
ret, stream = self.readConfig()
|
||||||
|
if not ret:
|
||||||
|
raise ServerExecutionException("ERROR: test configuration failed")
|
||||||
|
# exit after test if no commands specified (test only):
|
||||||
|
if not len(self._args):
|
||||||
|
output("OK: configuration test is successful")
|
||||||
|
return ret
|
||||||
|
|
||||||
# Nothing to do here, process in client/server
|
# Nothing to do here, process in client/server
|
||||||
return None
|
return None
|
||||||
|
except ServerExecutionException:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
output("ERROR: %s" % (e,))
|
output("ERROR: %s" % (e,))
|
||||||
if verbose > 2:
|
if verbose > 2:
|
||||||
|
@ -246,7 +268,8 @@ class Fail2banCmdLine():
|
||||||
try:
|
try:
|
||||||
self.configurator.Reload()
|
self.configurator.Reload()
|
||||||
self.configurator.readAll()
|
self.configurator.readAll()
|
||||||
ret = self.configurator.getOptions(jail, self._conf)
|
ret = self.configurator.getOptions(jail, self._conf,
|
||||||
|
ignoreWrong=not self.cleanConfOnly)
|
||||||
self.configurator.convertToProtocol()
|
self.configurator.convertToProtocol()
|
||||||
stream = self.configurator.getConfigStream()
|
stream = self.configurator.getConfigStream()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -274,6 +297,7 @@ class Fail2banCmdLine():
|
||||||
def exit(code=0):
|
def exit(code=0):
|
||||||
logSys.debug("Exit with code %s", code)
|
logSys.debug("Exit with code %s", code)
|
||||||
# because of possible buffered output in python, we should flush it before exit:
|
# because of possible buffered output in python, we should flush it before exit:
|
||||||
|
logging.shutdown()
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
# exit
|
# exit
|
||||||
|
|
|
@ -240,6 +240,9 @@ class Fail2banRegex(object):
|
||||||
self.setDatePattern(opts.datepattern)
|
self.setDatePattern(opts.datepattern)
|
||||||
if opts.usedns:
|
if opts.usedns:
|
||||||
self._filter.setUseDns(opts.usedns)
|
self._filter.setUseDns(opts.usedns)
|
||||||
|
self._filter.returnRawHost = opts.raw
|
||||||
|
self._filter.checkFindTime = False
|
||||||
|
self._filter.checkAllRegex = True
|
||||||
|
|
||||||
def decode_line(self, line):
|
def decode_line(self, line):
|
||||||
return FileContainer.decode_line('<LOG>', self._encoding, line)
|
return FileContainer.decode_line('<LOG>', self._encoding, line)
|
||||||
|
@ -343,7 +346,8 @@ class Fail2banRegex(object):
|
||||||
orgLineBuffer = self._filter._Filter__lineBuffer
|
orgLineBuffer = self._filter._Filter__lineBuffer
|
||||||
fullBuffer = len(orgLineBuffer) >= self._filter.getMaxLines()
|
fullBuffer = len(orgLineBuffer) >= self._filter.getMaxLines()
|
||||||
try:
|
try:
|
||||||
line, ret = self._filter.processLine(line, date, checkAllRegex=True, returnRawHost=self._raw)
|
ret = self._filter.processLine(line, date)
|
||||||
|
line = self._filter.processedLine()
|
||||||
for match in ret:
|
for match in ret:
|
||||||
# Append True/False flag depending if line was matched by
|
# Append True/False flag depending if line was matched by
|
||||||
# more than one regex
|
# more than one regex
|
||||||
|
|
|
@ -144,27 +144,27 @@ class Fail2banServer(Fail2banCmdLine):
|
||||||
return cli
|
return cli
|
||||||
|
|
||||||
def start(self, argv):
|
def start(self, argv):
|
||||||
# Command line options
|
|
||||||
ret = self.initCmdLine(argv)
|
|
||||||
if ret is not None:
|
|
||||||
return ret
|
|
||||||
|
|
||||||
# Commands
|
|
||||||
args = self._args
|
|
||||||
|
|
||||||
cli = None
|
|
||||||
# Just start:
|
|
||||||
if len(args) == 1 and args[0] == 'start' and not self._conf.get("interactive", False):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# If client mode - whole processing over client:
|
|
||||||
if len(args) or self._conf.get("interactive", False):
|
|
||||||
cli = self._Fail2banClient()
|
|
||||||
return cli.start(argv)
|
|
||||||
|
|
||||||
# Start the server:
|
|
||||||
server = None
|
server = None
|
||||||
try:
|
try:
|
||||||
|
# Command line options
|
||||||
|
ret = self.initCmdLine(argv)
|
||||||
|
if ret is not None:
|
||||||
|
return ret
|
||||||
|
|
||||||
|
# Commands
|
||||||
|
args = self._args
|
||||||
|
|
||||||
|
cli = None
|
||||||
|
# Just start:
|
||||||
|
if len(args) == 1 and args[0] == 'start' and not self._conf.get("interactive", False):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# If client mode - whole processing over client:
|
||||||
|
if len(args) or self._conf.get("interactive", False):
|
||||||
|
cli = self._Fail2banClient()
|
||||||
|
return cli.start(argv)
|
||||||
|
|
||||||
|
# Start the server:
|
||||||
from ..server.utils import Utils
|
from ..server.utils import Utils
|
||||||
# background = True, if should be new process running in background, otherwise start in foreground
|
# background = True, if should be new process running in background, otherwise start in foreground
|
||||||
# process will be forked in daemonize, inside of Server module.
|
# process will be forked in daemonize, inside of Server module.
|
||||||
|
|
|
@ -27,7 +27,7 @@ __license__ = "GPL"
|
||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
|
|
||||||
from .configreader import DefinitionInitConfigReader
|
from .configreader import DefinitionInitConfigReader, _merge_dicts
|
||||||
from ..server.action import CommandAction
|
from ..server.action import CommandAction
|
||||||
from ..helpers import getLogger
|
from ..helpers import getLogger
|
||||||
|
|
||||||
|
@ -53,7 +53,9 @@ class FilterReader(DefinitionInitConfigReader):
|
||||||
return self.__file
|
return self.__file
|
||||||
|
|
||||||
def getCombined(self):
|
def getCombined(self):
|
||||||
combinedopts = dict(list(self._opts.items()) + list(self._initOpts.items()))
|
combinedopts = self._opts
|
||||||
|
if self._initOpts:
|
||||||
|
combinedopts = _merge_dicts(self._opts, self._initOpts)
|
||||||
if not len(combinedopts):
|
if not len(combinedopts):
|
||||||
return {}
|
return {}
|
||||||
opts = CommandAction.substituteRecursiveTags(combinedopts)
|
opts = CommandAction.substituteRecursiveTags(combinedopts)
|
||||||
|
|
|
@ -43,13 +43,13 @@ logSys = getLogger(__name__)
|
||||||
class JailReader(ConfigReader):
|
class JailReader(ConfigReader):
|
||||||
|
|
||||||
# regex, to extract list of options:
|
# regex, to extract list of options:
|
||||||
optionCRE = re.compile("^((?:\w|-|_|\.)+)(?:\[(.*)\])?$")
|
optionCRE = re.compile(r"^([\w\-_\.]+)(?:\[(.*)\])?\s*$", re.DOTALL)
|
||||||
# regex, to iterate over single option in option list, syntax:
|
# regex, to iterate over single option in option list, syntax:
|
||||||
# `action = act[p1="...", p2='...', p3=...]`, where the p3=... not contains `,` or ']'
|
# `action = act[p1="...", p2='...', p3=...]`, where the p3=... not contains `,` or ']'
|
||||||
# since v0.10 separator extended with `]\s*[` for support of multiple option groups, syntax
|
# since v0.10 separator extended with `]\s*[` for support of multiple option groups, syntax
|
||||||
# `action = act[p1=...][p2=...]`
|
# `action = act[p1=...][p2=...]`
|
||||||
optionExtractRE = re.compile(
|
optionExtractRE = re.compile(
|
||||||
r'([\w\-_\.]+)=(?:"([^"]*)"|\'([^\']*)\'|([^,\]]*))(?:,|\]\s*\[|$)')
|
r'([\w\-_\.]+)=(?:"([^"]*)"|\'([^\']*)\'|([^,\]]*))(?:,|\]\s*\[|$)', re.DOTALL)
|
||||||
|
|
||||||
def __init__(self, name, force_enable=False, **kwargs):
|
def __init__(self, name, force_enable=False, **kwargs):
|
||||||
ConfigReader.__init__(self, **kwargs)
|
ConfigReader.__init__(self, **kwargs)
|
||||||
|
@ -119,16 +119,22 @@ class JailReader(ConfigReader):
|
||||||
defsec = self._cfg.get_defaults()
|
defsec = self._cfg.get_defaults()
|
||||||
defsec["fail2ban_version"] = version
|
defsec["fail2ban_version"] = version
|
||||||
|
|
||||||
# Read first options only needed for merge defaults ('known/...' from filter):
|
try:
|
||||||
self.__opts = ConfigReader.getOptions(self, self.__name, opts1st, shouldExist=True)
|
|
||||||
if not self.__opts:
|
# Read first options only needed for merge defaults ('known/...' from filter):
|
||||||
return False
|
self.__opts = ConfigReader.getOptions(self, self.__name, opts1st, shouldExist=True)
|
||||||
|
if not self.__opts: # pragma: no cover
|
||||||
|
raise JailDefError("Init jail options failed")
|
||||||
|
|
||||||
if self.isEnabled():
|
if not self.isEnabled():
|
||||||
|
return True
|
||||||
|
|
||||||
# Read filter
|
# Read filter
|
||||||
if self.__opts["filter"]:
|
flt = self.__opts["filter"]
|
||||||
filterName, filterOpt = JailReader.extractOptions(
|
if flt:
|
||||||
self.__opts["filter"])
|
filterName, filterOpt = JailReader.extractOptions(flt)
|
||||||
|
if not filterName:
|
||||||
|
raise JailDefError("Invalid filter definition %r" % flt)
|
||||||
self.__filter = FilterReader(
|
self.__filter = FilterReader(
|
||||||
filterName, self.__name, filterOpt, share_config=self.share_config, basedir=self.getBaseDir())
|
filterName, self.__name, filterOpt, share_config=self.share_config, basedir=self.getBaseDir())
|
||||||
ret = self.__filter.read()
|
ret = self.__filter.read()
|
||||||
|
@ -136,16 +142,15 @@ class JailReader(ConfigReader):
|
||||||
self.__filter.getOptions(self.__opts)
|
self.__filter.getOptions(self.__opts)
|
||||||
ConfigReader.merge_section(self, self.__name, self.__filter.getCombined(), 'known/')
|
ConfigReader.merge_section(self, self.__name, self.__filter.getCombined(), 'known/')
|
||||||
if not ret:
|
if not ret:
|
||||||
logSys.error("Unable to read the filter")
|
raise JailDefError("Unable to read the filter %r" % filterName)
|
||||||
return False
|
|
||||||
else:
|
else:
|
||||||
self.__filter = None
|
self.__filter = None
|
||||||
logSys.warning("No filter set for jail %s" % self.__name)
|
logSys.warning("No filter set for jail %s" % self.__name)
|
||||||
|
|
||||||
# Read second all options (so variables like %(known/param) can be interpolated):
|
# Read second all options (so variables like %(known/param) can be interpolated):
|
||||||
self.__opts = ConfigReader.getOptions(self, self.__name, opts)
|
self.__opts = ConfigReader.getOptions(self, self.__name, opts)
|
||||||
if not self.__opts:
|
if not self.__opts: # pragma: no cover
|
||||||
return False
|
raise JailDefError("Read jail options failed")
|
||||||
|
|
||||||
# cumulate filter options again (ignore given in jail):
|
# cumulate filter options again (ignore given in jail):
|
||||||
if self.__filter:
|
if self.__filter:
|
||||||
|
@ -157,6 +162,8 @@ class JailReader(ConfigReader):
|
||||||
if not act: # skip empty actions
|
if not act: # skip empty actions
|
||||||
continue
|
continue
|
||||||
actName, actOpt = JailReader.extractOptions(act)
|
actName, actOpt = JailReader.extractOptions(act)
|
||||||
|
if not actName:
|
||||||
|
raise JailDefError("Invalid action definition %r" % act)
|
||||||
if actName.endswith(".py"):
|
if actName.endswith(".py"):
|
||||||
self.__actions.append([
|
self.__actions.append([
|
||||||
"set",
|
"set",
|
||||||
|
@ -176,13 +183,22 @@ class JailReader(ConfigReader):
|
||||||
action.getOptions(self.__opts)
|
action.getOptions(self.__opts)
|
||||||
self.__actions.append(action)
|
self.__actions.append(action)
|
||||||
else:
|
else:
|
||||||
raise AttributeError("Unable to read action")
|
raise JailDefError("Unable to read action %r" % actName)
|
||||||
|
except JailDefError:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logSys.error("Error in action definition " + act)
|
logSys.debug("Caught exception: %s", e, exc_info=True)
|
||||||
logSys.debug("Caught exception: %s" % (e,))
|
raise ValueError("Error in action definition %r: %r" % (act, e))
|
||||||
return False
|
|
||||||
if not len(self.__actions):
|
if not len(self.__actions):
|
||||||
logSys.warning("No actions were defined for %s" % self.__name)
|
logSys.warning("No actions were defined for %s" % self.__name)
|
||||||
|
|
||||||
|
except JailDefError as e:
|
||||||
|
e = str(e)
|
||||||
|
logSys.error(e)
|
||||||
|
if not self.__opts:
|
||||||
|
self.__opts = dict()
|
||||||
|
self.__opts['config-error'] = e
|
||||||
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def convert(self, allow_no_files=False):
|
def convert(self, allow_no_files=False):
|
||||||
|
@ -196,6 +212,10 @@ class JailReader(ConfigReader):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
stream = []
|
stream = []
|
||||||
|
e = self.__opts.get('config-error')
|
||||||
|
if e:
|
||||||
|
stream.extend([['config-error', "Jail '%s' skipped, because of wrong configuration: %s" % (self.__name, e)]])
|
||||||
|
return stream
|
||||||
if self.__filter:
|
if self.__filter:
|
||||||
stream.extend(self.__filter.convert())
|
stream.extend(self.__filter.convert())
|
||||||
for opt, value in self.__opts.iteritems():
|
for opt, value in self.__opts.iteritems():
|
||||||
|
@ -257,3 +277,7 @@ class JailReader(ConfigReader):
|
||||||
val for val in optmatch.group(2,3,4) if val is not None][0]
|
val for val in optmatch.group(2,3,4) if val is not None][0]
|
||||||
option_opts[opt.strip()] = value.strip()
|
option_opts[opt.strip()] = value.strip()
|
||||||
return option_name, option_opts
|
return option_name, option_opts
|
||||||
|
|
||||||
|
|
||||||
|
class JailDefError(Exception):
|
||||||
|
pass
|
||||||
|
|
|
@ -54,7 +54,7 @@ class JailsReader(ConfigReader):
|
||||||
self.__jails = list()
|
self.__jails = list()
|
||||||
return ConfigReader.read(self, "jail")
|
return ConfigReader.read(self, "jail")
|
||||||
|
|
||||||
def getOptions(self, section=None):
|
def getOptions(self, section=None, ignoreWrong=True):
|
||||||
"""Reads configuration for jail(s) and adds enabled jails to __jails
|
"""Reads configuration for jail(s) and adds enabled jails to __jails
|
||||||
"""
|
"""
|
||||||
opts = []
|
opts = []
|
||||||
|
@ -66,7 +66,7 @@ class JailsReader(ConfigReader):
|
||||||
sections = [ section ]
|
sections = [ section ]
|
||||||
|
|
||||||
# Get the options of all jails.
|
# Get the options of all jails.
|
||||||
parse_status = True
|
parse_status = 0
|
||||||
for sec in sections:
|
for sec in sections:
|
||||||
if sec == 'INCLUDES':
|
if sec == 'INCLUDES':
|
||||||
continue
|
continue
|
||||||
|
@ -77,12 +77,16 @@ class JailsReader(ConfigReader):
|
||||||
ret = jail.getOptions()
|
ret = jail.getOptions()
|
||||||
if ret:
|
if ret:
|
||||||
if jail.isEnabled():
|
if jail.isEnabled():
|
||||||
|
# at least one jail was successful:
|
||||||
|
parse_status |= 1
|
||||||
# We only add enabled jails
|
# We only add enabled jails
|
||||||
self.__jails.append(jail)
|
self.__jails.append(jail)
|
||||||
else:
|
else:
|
||||||
logSys.error("Errors in jail %r. Skipping..." % sec)
|
logSys.error("Errors in jail %r.%s", sec, " Skipping..." if ignoreWrong else "")
|
||||||
parse_status = False
|
self.__jails.append(jail)
|
||||||
return parse_status
|
# at least one jail was invalid:
|
||||||
|
parse_status |= 2
|
||||||
|
return ((ignoreWrong and parse_status & 1) or not (parse_status & 2))
|
||||||
|
|
||||||
def convert(self, allow_no_files=False):
|
def convert(self, allow_no_files=False):
|
||||||
"""Convert read before __opts and jails to the commands stream
|
"""Convert read before __opts and jails to the commands stream
|
||||||
|
@ -95,15 +99,13 @@ class JailsReader(ConfigReader):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
stream = list()
|
stream = list()
|
||||||
for opt in self.__opts:
|
|
||||||
if opt == "":
|
|
||||||
stream.append([])
|
|
||||||
# Convert jails
|
# Convert jails
|
||||||
for jail in self.__jails:
|
for jail in self.__jails:
|
||||||
stream.extend(jail.convert(allow_no_files=allow_no_files))
|
stream.extend(jail.convert(allow_no_files=allow_no_files))
|
||||||
# Start jails
|
# Start jails
|
||||||
for jail in self.__jails:
|
for jail in self.__jails:
|
||||||
stream.append(["start", jail.getName()])
|
if not jail.options.get('config-error'):
|
||||||
|
stream.append(["start", jail.getName()])
|
||||||
|
|
||||||
return stream
|
return stream
|
||||||
|
|
||||||
|
|
|
@ -584,7 +584,7 @@ class CommandAction(ActionBase):
|
||||||
return self.executeCmd(realCmd, self.timeout)
|
return self.executeCmd(realCmd, self.timeout)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def executeCmd(realCmd, timeout=60):
|
def executeCmd(realCmd, timeout=60, **kwargs):
|
||||||
"""Executes a command.
|
"""Executes a command.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
@ -613,6 +613,6 @@ class CommandAction(ActionBase):
|
||||||
|
|
||||||
_cmd_lock.acquire()
|
_cmd_lock.acquire()
|
||||||
try:
|
try:
|
||||||
return Utils.executeCmd(realCmd, timeout, shell=True, output=False)
|
return Utils.executeCmd(realCmd, timeout, shell=True, output=False, **kwargs)
|
||||||
finally:
|
finally:
|
||||||
_cmd_lock.release()
|
_cmd_lock.release()
|
||||||
|
|
|
@ -193,7 +193,7 @@ class Actions(JailThread, Mapping):
|
||||||
def setBanTime(self, value):
|
def setBanTime(self, value):
|
||||||
value = MyTime.str2seconds(value)
|
value = MyTime.str2seconds(value)
|
||||||
self.__banManager.setBanTime(value)
|
self.__banManager.setBanTime(value)
|
||||||
logSys.info("Set banTime = %s" % value)
|
logSys.info(" banTime: %s" % value)
|
||||||
|
|
||||||
##
|
##
|
||||||
# Get the ban time.
|
# Get the ban time.
|
||||||
|
|
|
@ -241,7 +241,7 @@ class AsyncServer(asyncore.dispatcher):
|
||||||
def _remove_sock(self):
|
def _remove_sock(self):
|
||||||
try:
|
try:
|
||||||
os.remove(self.__sock)
|
os.remove(self.__sock)
|
||||||
except OSError as e:
|
except OSError as e: # pragma: no cover
|
||||||
if e.errno != errno.ENOENT:
|
if e.errno != errno.ENOENT:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
|
@ -89,6 +89,12 @@ class Filter(JailThread):
|
||||||
## Error counter (protected, so can be used in filter implementations)
|
## Error counter (protected, so can be used in filter implementations)
|
||||||
## if it reached 100 (at once), run-cycle will go idle
|
## if it reached 100 (at once), run-cycle will go idle
|
||||||
self._errors = 0
|
self._errors = 0
|
||||||
|
## return raw host (host is not dns):
|
||||||
|
self.returnRawHost = False
|
||||||
|
## check each regex (used for test purposes):
|
||||||
|
self.checkAllRegex = False
|
||||||
|
## if true ignores obsolete failures (failure time < now - findTime):
|
||||||
|
self.checkFindTime = True
|
||||||
## Ticks counter
|
## Ticks counter
|
||||||
self.ticks = 0
|
self.ticks = 0
|
||||||
|
|
||||||
|
@ -442,14 +448,14 @@ class Filter(JailThread):
|
||||||
if self.__ignoreCommand:
|
if self.__ignoreCommand:
|
||||||
command = CommandAction.replaceTag(self.__ignoreCommand, { 'ip': ip } )
|
command = CommandAction.replaceTag(self.__ignoreCommand, { 'ip': ip } )
|
||||||
logSys.debug('ignore command: ' + command)
|
logSys.debug('ignore command: ' + command)
|
||||||
ret_ignore = CommandAction.executeCmd(command)
|
ret, ret_ignore = CommandAction.executeCmd(command, success_codes=(0, 1))
|
||||||
|
ret_ignore = ret and ret_ignore == 0
|
||||||
self.logIgnoreIp(ip, log_ignore and ret_ignore, ignore_source="command")
|
self.logIgnoreIp(ip, log_ignore and ret_ignore, ignore_source="command")
|
||||||
return ret_ignore
|
return ret_ignore
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def processLine(self, line, date=None, returnRawHost=False,
|
def processLine(self, line, date=None):
|
||||||
checkAllRegex=False, checkFindTime=False):
|
|
||||||
"""Split the time portion from log msg and return findFailures on them
|
"""Split the time portion from log msg and return findFailures on them
|
||||||
"""
|
"""
|
||||||
if date:
|
if date:
|
||||||
|
@ -469,14 +475,15 @@ class Filter(JailThread):
|
||||||
else:
|
else:
|
||||||
tupleLine = (l, "", "", None)
|
tupleLine = (l, "", "", None)
|
||||||
|
|
||||||
return "".join(tupleLine[::2]), self.findFailure(
|
# save last line (lazy convert of process line tuple to string on demand):
|
||||||
tupleLine, date, returnRawHost, checkAllRegex, checkFindTime)
|
self.processedLine = lambda: "".join(tupleLine[::2])
|
||||||
|
return self.findFailure(tupleLine, date)
|
||||||
|
|
||||||
def processLineAndAdd(self, line, date=None):
|
def processLineAndAdd(self, line, date=None):
|
||||||
"""Processes the line for failures and populates failManager
|
"""Processes the line for failures and populates failManager
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
for element in self.processLine(line, date, checkFindTime=True)[1]:
|
for element in self.processLine(line, date):
|
||||||
ip = element[1]
|
ip = element[1]
|
||||||
unixTime = element[2]
|
unixTime = element[2]
|
||||||
lines = element[3]
|
lines = element[3]
|
||||||
|
@ -532,10 +539,10 @@ class Filter(JailThread):
|
||||||
# to find the logging time.
|
# to find the logging time.
|
||||||
# @return a dict with IP and timestamp.
|
# @return a dict with IP and timestamp.
|
||||||
|
|
||||||
def findFailure(self, tupleLine, date=None, returnRawHost=False,
|
def findFailure(self, tupleLine, date=None):
|
||||||
checkAllRegex=False, checkFindTime=False):
|
|
||||||
failList = list()
|
failList = list()
|
||||||
|
|
||||||
|
returnRawHost = self.returnRawHost
|
||||||
cidr = IPAddr.CIDR_UNSPEC
|
cidr = IPAddr.CIDR_UNSPEC
|
||||||
if self.__useDns == "raw":
|
if self.__useDns == "raw":
|
||||||
returnRawHost = True
|
returnRawHost = True
|
||||||
|
@ -570,7 +577,7 @@ class Filter(JailThread):
|
||||||
timeText = self.__lastTimeText or "".join(tupleLine[::2])
|
timeText = self.__lastTimeText or "".join(tupleLine[::2])
|
||||||
date = self.__lastDate
|
date = self.__lastDate
|
||||||
|
|
||||||
if checkFindTime and date is not None and date < MyTime.time() - self.getFindTime():
|
if self.checkFindTime and date is not None and date < MyTime.time() - self.getFindTime():
|
||||||
logSys.log(5, "Ignore line since time %s < %s - %s",
|
logSys.log(5, "Ignore line since time %s < %s - %s",
|
||||||
date, MyTime.time(), self.getFindTime())
|
date, MyTime.time(), self.getFindTime())
|
||||||
return failList
|
return failList
|
||||||
|
@ -591,7 +598,7 @@ class Filter(JailThread):
|
||||||
# The ignoreregex matched. Remove ignored match.
|
# The ignoreregex matched. Remove ignored match.
|
||||||
self.__lineBuffer = failRegex.getUnmatchedTupleLines()
|
self.__lineBuffer = failRegex.getUnmatchedTupleLines()
|
||||||
logSys.log(7, "Matched ignoreregex and was ignored")
|
logSys.log(7, "Matched ignoreregex and was ignored")
|
||||||
if not checkAllRegex:
|
if not self.checkAllRegex:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
@ -634,7 +641,7 @@ class Filter(JailThread):
|
||||||
ip = IPAddr(fid, IPAddr.CIDR_RAW)
|
ip = IPAddr(fid, IPAddr.CIDR_RAW)
|
||||||
failList.append([failRegexIndex, ip, date,
|
failList.append([failRegexIndex, ip, date,
|
||||||
failRegex.getMatchedLines(), fail])
|
failRegex.getMatchedLines(), fail])
|
||||||
if not checkAllRegex:
|
if not self.checkAllRegex:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
ips = DNSUtils.textToIp(host, self.__useDns)
|
ips = DNSUtils.textToIp(host, self.__useDns)
|
||||||
|
@ -642,7 +649,7 @@ class Filter(JailThread):
|
||||||
for ip in ips:
|
for ip in ips:
|
||||||
failList.append([failRegexIndex, ip, date,
|
failList.append([failRegexIndex, ip, date,
|
||||||
failRegex.getMatchedLines(), fail])
|
failRegex.getMatchedLines(), fail])
|
||||||
if not checkAllRegex:
|
if not self.checkAllRegex:
|
||||||
break
|
break
|
||||||
except RegexException as e: # pragma: no cover - unsure if reachable
|
except RegexException as e: # pragma: no cover - unsure if reachable
|
||||||
logSys.error(e)
|
logSys.error(e)
|
||||||
|
|
|
@ -530,17 +530,19 @@ class Server:
|
||||||
# @param target the logging target
|
# @param target the logging target
|
||||||
|
|
||||||
def setLogTarget(self, target):
|
def setLogTarget(self, target):
|
||||||
|
# check reserved targets in uppercase, don't change target, because it can be file:
|
||||||
|
systarget = target.upper()
|
||||||
with self.__loggingLock:
|
with self.__loggingLock:
|
||||||
# don't set new handlers if already the same
|
# don't set new handlers if already the same
|
||||||
# or if "INHERITED" (foreground worker of the test cases, to prevent stop logging):
|
# or if "INHERITED" (foreground worker of the test cases, to prevent stop logging):
|
||||||
if self.__logTarget == target:
|
if self.__logTarget == target:
|
||||||
return True
|
return True
|
||||||
if target == "INHERITED":
|
if systarget == "INHERITED":
|
||||||
self.__logTarget = target
|
self.__logTarget = target
|
||||||
return True
|
return True
|
||||||
# set a format which is simpler for console use
|
# set a format which is simpler for console use
|
||||||
fmt = "%(asctime)s %(name)-24s[%(process)d]: %(levelname)-7s %(message)s"
|
fmt = "%(asctime)s %(name)-24s[%(process)d]: %(levelname)-7s %(message)s"
|
||||||
if target == "SYSLOG":
|
if systarget == "SYSLOG":
|
||||||
# Syslog daemons already add date to the message.
|
# Syslog daemons already add date to the message.
|
||||||
fmt = "%(name)s[%(process)d]: %(levelname)s %(message)s"
|
fmt = "%(name)s[%(process)d]: %(levelname)s %(message)s"
|
||||||
facility = logging.handlers.SysLogHandler.LOG_DAEMON
|
facility = logging.handlers.SysLogHandler.LOG_DAEMON
|
||||||
|
@ -559,9 +561,9 @@ class Server:
|
||||||
"Syslog socket file: %s does not exists"
|
"Syslog socket file: %s does not exists"
|
||||||
" or is not a socket" % self.__syslogSocket)
|
" or is not a socket" % self.__syslogSocket)
|
||||||
return False
|
return False
|
||||||
elif target == "STDOUT":
|
elif systarget == "STDOUT":
|
||||||
hdlr = logging.StreamHandler(sys.stdout)
|
hdlr = logging.StreamHandler(sys.stdout)
|
||||||
elif target == "STDERR":
|
elif systarget == "STDERR":
|
||||||
hdlr = logging.StreamHandler(sys.stderr)
|
hdlr = logging.StreamHandler(sys.stderr)
|
||||||
else:
|
else:
|
||||||
# Target should be a file
|
# Target should be a file
|
||||||
|
|
|
@ -131,6 +131,9 @@ class Transmitter:
|
||||||
return self.status(command[1:])
|
return self.status(command[1:])
|
||||||
elif command[0] == "version":
|
elif command[0] == "version":
|
||||||
return version.version
|
return version.version
|
||||||
|
elif command[0] == "config-error":
|
||||||
|
logSys.error(command[1])
|
||||||
|
return None
|
||||||
raise Exception("Invalid command")
|
raise Exception("Invalid command")
|
||||||
|
|
||||||
def __commandSet(self, command, multiple=False):
|
def __commandSet(self, command, multiple=False):
|
||||||
|
|
|
@ -110,7 +110,7 @@ class Utils():
|
||||||
return flags
|
return flags
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def executeCmd(realCmd, timeout=60, shell=True, output=False, tout_kill_tree=True):
|
def executeCmd(realCmd, timeout=60, shell=True, output=False, tout_kill_tree=True, success_codes=(0,)):
|
||||||
"""Executes a command.
|
"""Executes a command.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
@ -170,7 +170,7 @@ class Utils():
|
||||||
time.sleep(Utils.DEFAULT_SLEEP_INTERVAL)
|
time.sleep(Utils.DEFAULT_SLEEP_INTERVAL)
|
||||||
retcode = popen.poll()
|
retcode = popen.poll()
|
||||||
#logSys.debug("%s -- killed %s ", realCmd, retcode)
|
#logSys.debug("%s -- killed %s ", realCmd, retcode)
|
||||||
if retcode is None and not Utils.pid_exists(pgid):
|
if retcode is None and not Utils.pid_exists(pgid): # pragma: no cover
|
||||||
retcode = signal.SIGKILL
|
retcode = signal.SIGKILL
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
stderr = "%s -- failed with %s" % (realCmd, e)
|
stderr = "%s -- failed with %s" % (realCmd, e)
|
||||||
|
@ -178,7 +178,7 @@ class Utils():
|
||||||
if not popen:
|
if not popen:
|
||||||
return False if not output else (False, stdout, stderr, retcode)
|
return False if not output else (False, stdout, stderr, retcode)
|
||||||
|
|
||||||
std_level = retcode == 0 and logging.DEBUG or logging.ERROR
|
std_level = logging.DEBUG if retcode in success_codes else logging.ERROR
|
||||||
# if we need output (to return or to log it):
|
# if we need output (to return or to log it):
|
||||||
if output or std_level >= logSys.getEffectiveLevel():
|
if output or std_level >= logSys.getEffectiveLevel():
|
||||||
# if was timeouted (killed/terminated) - to prevent waiting, set std handles to non-blocking mode.
|
# if was timeouted (killed/terminated) - to prevent waiting, set std handles to non-blocking mode.
|
||||||
|
@ -208,8 +208,8 @@ class Utils():
|
||||||
popen.stderr.close()
|
popen.stderr.close()
|
||||||
|
|
||||||
success = False
|
success = False
|
||||||
if retcode == 0:
|
if retcode in success_codes:
|
||||||
logSys.debug("%-.40s -- returned successfully", realCmd)
|
logSys.debug("%-.40s -- returned successfully %i", realCmd, retcode)
|
||||||
success = True
|
success = True
|
||||||
elif retcode is None:
|
elif retcode is None:
|
||||||
logSys.error("%-.40s -- unable to kill PID %i", realCmd, popen.pid)
|
logSys.error("%-.40s -- unable to kill PID %i", realCmd, popen.pid)
|
||||||
|
@ -223,7 +223,9 @@ class Utils():
|
||||||
logSys.error("%-.40s -- returned %i", realCmd, retcode)
|
logSys.error("%-.40s -- returned %i", realCmd, retcode)
|
||||||
if msg:
|
if msg:
|
||||||
logSys.info("HINT on %i: %s", retcode, msg % locals())
|
logSys.info("HINT on %i: %s", retcode, msg % locals())
|
||||||
return success if not output else (success, stdout, stderr, retcode)
|
if output:
|
||||||
|
return success, stdout, stderr, retcode
|
||||||
|
return success if len(success_codes) == 1 else (success, retcode)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def wait_for(cond, timeout, interval=None):
|
def wait_for(cond, timeout, interval=None):
|
||||||
|
|
|
@ -21,6 +21,7 @@ import os
|
||||||
import smtpd
|
import smtpd
|
||||||
import threading
|
import threading
|
||||||
import unittest
|
import unittest
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
if sys.version_info >= (3, 3):
|
if sys.version_info >= (3, 3):
|
||||||
import importlib
|
import importlib
|
||||||
|
@ -38,7 +39,9 @@ class TestSMTPServer(smtpd.SMTPServer):
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.mailfrom = mailfrom
|
self.mailfrom = mailfrom
|
||||||
self.rcpttos = rcpttos
|
self.rcpttos = rcpttos
|
||||||
self.data = data
|
self.org_data = data
|
||||||
|
# replace new line (with tab or space) for possible mime translations (word wrap):
|
||||||
|
self.data = re.sub(r"\n[\t ]", " ", data)
|
||||||
|
|
||||||
|
|
||||||
class SMTPActionTest(unittest.TestCase):
|
class SMTPActionTest(unittest.TestCase):
|
||||||
|
@ -105,9 +108,9 @@ class SMTPActionTest(unittest.TestCase):
|
||||||
self.assertEqual(self.smtpd.rcpttos, ["root"])
|
self.assertEqual(self.smtpd.rcpttos, ["root"])
|
||||||
subject = "Subject: [Fail2Ban] %s: banned %s" % (
|
subject = "Subject: [Fail2Ban] %s: banned %s" % (
|
||||||
self.jail.name, aInfo['ip'])
|
self.jail.name, aInfo['ip'])
|
||||||
self.assertIn(subject, self.smtpd.data.replace("\n", ""))
|
self.assertIn(subject, self.smtpd.data)
|
||||||
self.assertTrue(
|
self.assertIn(
|
||||||
"%i attempts" % aInfo['failures'] in self.smtpd.data)
|
"%i attempts" % aInfo['failures'], self.smtpd.data)
|
||||||
|
|
||||||
self.action.matches = "matches"
|
self.action.matches = "matches"
|
||||||
self.action.ban(aInfo)
|
self.action.ban(aInfo)
|
||||||
|
|
|
@ -194,13 +194,15 @@ class JailReaderTest(LogCaptureTestCase):
|
||||||
self.assertTrue(jail.read())
|
self.assertTrue(jail.read())
|
||||||
self.assertFalse(jail.getOptions())
|
self.assertFalse(jail.getOptions())
|
||||||
self.assertTrue(jail.isEnabled())
|
self.assertTrue(jail.isEnabled())
|
||||||
self.assertLogged('Error in action definition joho[foo')
|
self.assertLogged("Invalid action definition 'joho[foo'")
|
||||||
# This unittest has been deactivated for some time...
|
|
||||||
# self.assertLogged(
|
def testJailFilterBrokenDef(self):
|
||||||
# 'Caught exception: While reading action joho[foo we should have got 1 or 2 groups. Got: 0')
|
jail = JailReader('brokenfilterdef', basedir=IMPERFECT_CONFIG,
|
||||||
# let's test for what is actually logged and handle changes in the future
|
share_config=IMPERFECT_CONFIG_SHARE_CFG)
|
||||||
self.assertLogged(
|
self.assertTrue(jail.read())
|
||||||
"Caught exception: 'NoneType' object has no attribute 'endswith'")
|
self.assertFalse(jail.getOptions())
|
||||||
|
self.assertTrue(jail.isEnabled())
|
||||||
|
self.assertLogged("Invalid filter definition 'flt[test'")
|
||||||
|
|
||||||
if STOCK:
|
if STOCK:
|
||||||
def testStockSSHJail(self):
|
def testStockSSHJail(self):
|
||||||
|
@ -497,7 +499,7 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||||
def testReadTestJailConf(self):
|
def testReadTestJailConf(self):
|
||||||
jails = JailsReader(basedir=IMPERFECT_CONFIG, share_config=IMPERFECT_CONFIG_SHARE_CFG)
|
jails = JailsReader(basedir=IMPERFECT_CONFIG, share_config=IMPERFECT_CONFIG_SHARE_CFG)
|
||||||
self.assertTrue(jails.read())
|
self.assertTrue(jails.read())
|
||||||
self.assertFalse(jails.getOptions())
|
self.assertFalse(jails.getOptions(ignoreWrong=False))
|
||||||
self.assertRaises(ValueError, jails.convert)
|
self.assertRaises(ValueError, jails.convert)
|
||||||
comm_commands = jails.convert(allow_no_files=True)
|
comm_commands = jails.convert(allow_no_files=True)
|
||||||
self.maxDiff = None
|
self.maxDiff = None
|
||||||
|
@ -526,8 +528,18 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||||
['start', 'emptyaction'],
|
['start', 'emptyaction'],
|
||||||
['start', 'missinglogfiles'],
|
['start', 'missinglogfiles'],
|
||||||
['start', 'brokenaction'],
|
['start', 'brokenaction'],
|
||||||
['start', 'parse_to_end_of_jail.conf'],]))
|
['start', 'parse_to_end_of_jail.conf'],
|
||||||
self.assertLogged("Errors in jail 'missingbitsjail'. Skipping...")
|
['config-error',
|
||||||
|
"Jail 'brokenactiondef' skipped, because of wrong configuration: Invalid action definition 'joho[foo'"],
|
||||||
|
['config-error',
|
||||||
|
"Jail 'brokenfilterdef' skipped, because of wrong configuration: Invalid filter definition 'flt[test'"],
|
||||||
|
['config-error',
|
||||||
|
"Jail 'missingaction' skipped, because of wrong configuration: Unable to read action 'noactionfileforthisaction'"],
|
||||||
|
['config-error',
|
||||||
|
"Jail 'missingbitsjail' skipped, because of wrong configuration: Unable to read the filter 'catchallthebadies'"],
|
||||||
|
]))
|
||||||
|
self.assertLogged("Errors in jail 'missingbitsjail'.")
|
||||||
|
self.assertNotLogged("Skipping...")
|
||||||
self.assertLogged("No file(s) found for glob /weapons/of/mass/destruction")
|
self.assertLogged("No file(s) found for glob /weapons/of/mass/destruction")
|
||||||
|
|
||||||
if STOCK:
|
if STOCK:
|
||||||
|
|
|
@ -27,10 +27,18 @@ logpath = /weapons/of/mass/destruction
|
||||||
enabled = true
|
enabled = true
|
||||||
action = joho[foo
|
action = joho[foo
|
||||||
|
|
||||||
|
[brokenfilterdef]
|
||||||
|
enabled = true
|
||||||
|
filter = flt[test
|
||||||
|
|
||||||
[brokenaction]
|
[brokenaction]
|
||||||
enabled = true
|
enabled = true
|
||||||
action = brokenaction
|
action = brokenaction
|
||||||
|
|
||||||
|
[missingaction]
|
||||||
|
enabled = true
|
||||||
|
action = noactionfileforthisaction
|
||||||
|
|
||||||
[missingbitsjail]
|
[missingbitsjail]
|
||||||
enabled = true
|
enabled = true
|
||||||
filter = catchallthebadies
|
filter = catchallthebadies
|
||||||
|
|
|
@ -675,6 +675,36 @@ class Fail2banServerTest(Fail2banClientServerBase):
|
||||||
self.pruneLog()
|
self.pruneLog()
|
||||||
os.remove(pjoin(tmp, "f2b.sock"))
|
os.remove(pjoin(tmp, "f2b.sock"))
|
||||||
|
|
||||||
|
@with_tmpdir
|
||||||
|
@with_kill_srv
|
||||||
|
def testServerTestFailStart(self, tmp):
|
||||||
|
# started directly here, so prevent overwrite test cases logger with "INHERITED"
|
||||||
|
startparams = _start_params(tmp, logtarget="INHERITED")
|
||||||
|
cfg = pjoin(tmp, "config")
|
||||||
|
|
||||||
|
# test configuration is correct:
|
||||||
|
self.pruneLog("[test-phase 0]")
|
||||||
|
self.execSuccess(startparams, "--test")
|
||||||
|
self.assertLogged("OK: configuration test is successful")
|
||||||
|
|
||||||
|
# append one wrong configured jail:
|
||||||
|
_write_file(pjoin(cfg, "jail.conf"), "a", "", "[broken-jail]",
|
||||||
|
"", "filter = broken-jail-filter", "enabled = true")
|
||||||
|
|
||||||
|
# first try test config:
|
||||||
|
self.pruneLog("[test-phase 0a]")
|
||||||
|
self.execFailed(startparams, "--test")
|
||||||
|
self.assertLogged("Unable to read the filter 'broken-jail-filter'",
|
||||||
|
"Errors in jail 'broken-jail'.",
|
||||||
|
"ERROR: test configuration failed", all=True)
|
||||||
|
|
||||||
|
# failed to start with test config:
|
||||||
|
self.pruneLog("[test-phase 0b]")
|
||||||
|
self.execFailed(startparams, "-t", "start")
|
||||||
|
self.assertLogged("Unable to read the filter 'broken-jail-filter'",
|
||||||
|
"Errors in jail 'broken-jail'.",
|
||||||
|
"ERROR: test configuration failed", all=True)
|
||||||
|
|
||||||
@with_tmpdir
|
@with_tmpdir
|
||||||
def testKillAfterStart(self, tmp):
|
def testKillAfterStart(self, tmp):
|
||||||
try:
|
try:
|
||||||
|
@ -769,6 +799,10 @@ class Fail2banServerTest(Fail2banClientServerBase):
|
||||||
_write_action_cfg(actname="test-action2")
|
_write_action_cfg(actname="test-action2")
|
||||||
|
|
||||||
_write_jail_cfg(enabled=[1], actions=[1,2])
|
_write_jail_cfg(enabled=[1], actions=[1,2])
|
||||||
|
# append one wrong configured jail:
|
||||||
|
_write_file(pjoin(cfg, "jail.conf"), "a", "", "[broken-jail]",
|
||||||
|
"", "filter = broken-jail-filter", "enabled = true")
|
||||||
|
|
||||||
_write_file(test1log, "w", *((str(int(MyTime.time())) + " failure 401 from 192.0.2.1: test 1",) * 3))
|
_write_file(test1log, "w", *((str(int(MyTime.time())) + " failure 401 from 192.0.2.1: test 1",) * 3))
|
||||||
_write_file(test2log, "w")
|
_write_file(test2log, "w")
|
||||||
_write_file(test3log, "w")
|
_write_file(test3log, "w")
|
||||||
|
@ -787,6 +821,12 @@ class Fail2banServerTest(Fail2banClientServerBase):
|
||||||
self.assertLogged(
|
self.assertLogged(
|
||||||
"stdout: '[test-jail1] test-action1: ** start'",
|
"stdout: '[test-jail1] test-action1: ** start'",
|
||||||
"stdout: '[test-jail1] test-action2: ** start'", all=True)
|
"stdout: '[test-jail1] test-action2: ** start'", all=True)
|
||||||
|
|
||||||
|
# broken jail was logged (in client and server log):
|
||||||
|
self.assertLogged(
|
||||||
|
"Unable to read the filter 'broken-jail-filter'",
|
||||||
|
"Errors in jail 'broken-jail'. Skipping...",
|
||||||
|
"Jail 'broken-jail' skipped, because of wrong configuration", all=True)
|
||||||
|
|
||||||
# enable both jails, 3 logs for jail1, etc...
|
# enable both jails, 3 logs for jail1, etc...
|
||||||
# truncate test-log - we should not find unban/ban again by reload:
|
# truncate test-log - we should not find unban/ban again by reload:
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
#!/usr/bin/env fail2ban-python
|
#!/usr/bin/env fail2ban-python
|
||||||
import sys
|
import sys
|
||||||
|
if len(sys.argv) != 2 or sys.argv[1] == "":
|
||||||
|
sys.stderr.write('usage: ignorecommand IP')
|
||||||
|
exit(10)
|
||||||
if sys.argv[1] == "10.0.0.1":
|
if sys.argv[1] == "10.0.0.1":
|
||||||
exit(0)
|
exit(0)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
|
@ -40,6 +40,8 @@ Feb 19 18:01:50 batman sm-mta[78152]: ruleset=check_relay, arg1=[196.213.73.146]
|
||||||
|
|
||||||
# failJSON: { "time": "2005-02-27T10:53:06", "match": true , "host": "209.15.212.253" }
|
# failJSON: { "time": "2005-02-27T10:53:06", "match": true , "host": "209.15.212.253" }
|
||||||
Feb 27 10:53:06 batman sm-mta[44307]: s1R9r60D044307: rejecting commands from [209.15.212.253] due to pre-greeting traffic after 0 seconds
|
Feb 27 10:53:06 batman sm-mta[44307]: s1R9r60D044307: rejecting commands from [209.15.212.253] due to pre-greeting traffic after 0 seconds
|
||||||
|
# failJSON: { "time": "2005-02-27T10:53:07", "match": true , "host": "1.2.3.4" }
|
||||||
|
Feb 27 10:53:07 strange sm-mta[18001]: u9A0GtpL018001: rejecting commands from example.com [1.2.3.4] due to pre-greeting traffic after 6 seconds
|
||||||
|
|
||||||
# failJSON: { "time": "2005-02-27T15:44:18", "match": true , "host": "41.204.78.137" }
|
# failJSON: { "time": "2005-02-27T15:44:18", "match": true , "host": "41.204.78.137" }
|
||||||
Feb 27 15:44:18 batman sm-mta[87838]: s1REiHdq087838: ruleset=check_rcpt, arg1=<gert-jan@t-online.ch>, relay=[41.204.78.137], reject=550 5.7.1 <gert-jan@t-online.ch>... Relaying denied. IP name lookup failed [41.204.78.137]
|
Feb 27 15:44:18 batman sm-mta[87838]: s1REiHdq087838: ruleset=check_rcpt, arg1=<gert-jan@t-online.ch>, relay=[41.204.78.137], reject=550 5.7.1 <gert-jan@t-online.ch>... Relaying denied. IP name lookup failed [41.204.78.137]
|
||||||
|
|
|
@ -119,7 +119,13 @@ Sep 29 17:15:02 spaceman sshd[12946]: Failed password for user from 127.0.0.1 po
|
||||||
|
|
||||||
# failJSON: { "time": "2004-11-11T08:04:51", "match": true , "host": "127.0.0.1", "desc": "Injecting on username ssh 'from 10.10.1.1'@localhost" }
|
# failJSON: { "time": "2004-11-11T08:04:51", "match": true , "host": "127.0.0.1", "desc": "Injecting on username ssh 'from 10.10.1.1'@localhost" }
|
||||||
Nov 11 08:04:51 redbamboo sshd[2737]: Failed password for invalid user from 10.10.1.1 from 127.0.0.1 port 58946 ssh2
|
Nov 11 08:04:51 redbamboo sshd[2737]: Failed password for invalid user from 10.10.1.1 from 127.0.0.1 port 58946 ssh2
|
||||||
|
# failJSON: { "time": "2004-11-11T08:04:52", "match": true , "host": "127.0.0.1", "desc": "More complex injecting on username ssh 'test from 10.10.1.2 port 55555 ssh2'@localhost" }
|
||||||
|
Nov 11 08:04:52 redbamboo sshd[2737]: Failed password for invalid user test from 10.10.1.2 port 55555 ssh2 from 127.0.0.1 port 58946 ssh2
|
||||||
|
# failJSON: { "time": "2004-11-11T08:04:52", "match": true , "host": "127.0.0.1", "desc": "More complex injecting on auth-info ssh test@localhost, auth-info: ' from 10.10.1.2 port 55555 ssh2'" }
|
||||||
|
Nov 11 08:04:52 redbamboo sshd[2737]: Failed password for invalid user test from 127.0.0.1 port 58946 ssh2: from 10.10.1.2 port 55555 ssh2
|
||||||
|
|
||||||
|
# failJSON: { "time": "2005-07-05T18:22:44", "match": true , "host": "127.0.0.1", "desc": "Failed publickey for ..." }
|
||||||
|
Jul 05 18:22:44 mercury sshd[4669]: Failed publickey for graysky from 127.0.0.1 port 37954 ssh2: RSA SHA256:v3dpapGleDaUKf$4V1vKyR9ZyUgjaJAmoCTcb2PLljI
|
||||||
|
|
||||||
# failJSON: { "match": false }
|
# failJSON: { "match": false }
|
||||||
Nov 23 21:50:19 sshd[8148]: Disconnecting: Too many authentication failures for root [preauth]
|
Nov 23 21:50:19 sshd[8148]: Disconnecting: Too many authentication failures for root [preauth]
|
||||||
|
@ -163,4 +169,3 @@ Apr 27 13:02:04 host sshd[29116]: Received disconnect from 1.2.3.4: 11: Normal S
|
||||||
# Match sshd auth errors on OpenSUSE systems
|
# Match sshd auth errors on OpenSUSE systems
|
||||||
# failJSON: { "time": "2015-04-16T20:02:50", "match": true , "host": "222.186.21.217", "desc": "Authentication for user failed" }
|
# failJSON: { "time": "2015-04-16T20:02:50", "match": true , "host": "222.186.21.217", "desc": "Authentication for user failed" }
|
||||||
2015-04-16T18:02:50.321974+00:00 host sshd[2716]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=222.186.21.217 user=root
|
2015-04-16T18:02:50.321974+00:00 host sshd[2716]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=222.186.21.217 user=root
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Dec 31 11:55:01 [sshd] error: PAM: Authentication failure for test from 87.142.124.10
|
||||||
|
Dec 31 11:55:02 [sshd] error: PAM: Authentication failure for test from 87.142.124.10
|
||||||
|
Dec 31 11:55:03 [sshd] error: PAM: Authentication failure for test from 87.142.124.10
|
||||||
|
Dec 31 11:55:04 [sshd] error: PAM: Authentication failure for test from 87.142.124.10
|
|
@ -379,6 +379,10 @@ class IgnoreIP(LogCaptureTestCase):
|
||||||
self.filter.setIgnoreCommand(sys.executable + ' ' + os.path.join(TEST_FILES_DIR, "ignorecommand.py <ip>"))
|
self.filter.setIgnoreCommand(sys.executable + ' ' + os.path.join(TEST_FILES_DIR, "ignorecommand.py <ip>"))
|
||||||
self.assertTrue(self.filter.inIgnoreIPList("10.0.0.1"))
|
self.assertTrue(self.filter.inIgnoreIPList("10.0.0.1"))
|
||||||
self.assertFalse(self.filter.inIgnoreIPList("10.0.0.0"))
|
self.assertFalse(self.filter.inIgnoreIPList("10.0.0.0"))
|
||||||
|
self.assertLogged("returned successfully 0", "returned successfully 1", all=True)
|
||||||
|
self.pruneLog()
|
||||||
|
self.assertFalse(self.filter.inIgnoreIPList(""))
|
||||||
|
self.assertLogged("usage: ignorecommand IP", "returned 10", all=True)
|
||||||
|
|
||||||
def testIgnoreCauseOK(self):
|
def testIgnoreCauseOK(self):
|
||||||
ip = "93.184.216.34"
|
ip = "93.184.216.34"
|
||||||
|
|
|
@ -45,6 +45,9 @@ class FilterSamplesRegex(unittest.TestCase):
|
||||||
"""Call before every test case."""
|
"""Call before every test case."""
|
||||||
super(FilterSamplesRegex, self).setUp()
|
super(FilterSamplesRegex, self).setUp()
|
||||||
self.filter = Filter(None)
|
self.filter = Filter(None)
|
||||||
|
self.filter.returnRawHost = True
|
||||||
|
self.filter.checkAllRegex = True
|
||||||
|
self.filter.checkFindTime = False
|
||||||
self.filter.active = True
|
self.filter.active = True
|
||||||
|
|
||||||
setUpMyTime()
|
setUpMyTime()
|
||||||
|
@ -112,8 +115,7 @@ def testSampleRegexsFactory(name, basedir):
|
||||||
else:
|
else:
|
||||||
faildata = {}
|
faildata = {}
|
||||||
|
|
||||||
ret = self.filter.processLine(
|
ret = self.filter.processLine(line)
|
||||||
line, returnRawHost=True, checkAllRegex=True)[1]
|
|
||||||
if not ret:
|
if not ret:
|
||||||
# Check line is flagged as none match
|
# Check line is flagged as none match
|
||||||
self.assertFalse(faildata.get('match', True),
|
self.assertFalse(faildata.get('match', True),
|
||||||
|
|
|
@ -28,6 +28,7 @@ import unittest
|
||||||
import time
|
import time
|
||||||
import tempfile
|
import tempfile
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
|
@ -1611,31 +1612,114 @@ class ServerConfigReaderTests(LogCaptureTestCase):
|
||||||
# wrap default command processor:
|
# wrap default command processor:
|
||||||
action.executeCmd = self._executeCmd
|
action.executeCmd = self._executeCmd
|
||||||
# test start :
|
# test start :
|
||||||
logSys.debug('# === start ==='); self.pruneLog()
|
self.pruneLog('# === start ===')
|
||||||
action.start()
|
action.start()
|
||||||
self.assertLogged(*tests['start'], all=True)
|
self.assertLogged(*tests['start'], all=True)
|
||||||
# test ban ip4 :
|
# test ban ip4 :
|
||||||
logSys.debug('# === ban-ipv4 ==='); self.pruneLog()
|
self.pruneLog('# === ban-ipv4 ===')
|
||||||
action.ban({'ip': IPAddr('192.0.2.1')})
|
action.ban({'ip': IPAddr('192.0.2.1')})
|
||||||
self.assertLogged(*tests['ip4-check']+tests['ip4-ban'], all=True)
|
self.assertLogged(*tests['ip4-check']+tests['ip4-ban'], all=True)
|
||||||
self.assertNotLogged(*tests['ip6'], all=True)
|
self.assertNotLogged(*tests['ip6'], all=True)
|
||||||
# test unban ip4 :
|
# test unban ip4 :
|
||||||
logSys.debug('# === unban ipv4 ==='); self.pruneLog()
|
self.pruneLog('# === unban ipv4 ===')
|
||||||
action.unban({'ip': IPAddr('192.0.2.1')})
|
action.unban({'ip': IPAddr('192.0.2.1')})
|
||||||
self.assertLogged(*tests['ip4-check']+tests['ip4-unban'], all=True)
|
self.assertLogged(*tests['ip4-check']+tests['ip4-unban'], all=True)
|
||||||
self.assertNotLogged(*tests['ip6'], all=True)
|
self.assertNotLogged(*tests['ip6'], all=True)
|
||||||
# test ban ip6 :
|
# test ban ip6 :
|
||||||
logSys.debug('# === ban ipv6 ==='); self.pruneLog()
|
self.pruneLog('# === ban ipv6 ===')
|
||||||
action.ban({'ip': IPAddr('2001:DB8::')})
|
action.ban({'ip': IPAddr('2001:DB8::')})
|
||||||
self.assertLogged(*tests['ip6-check']+tests['ip6-ban'], all=True)
|
self.assertLogged(*tests['ip6-check']+tests['ip6-ban'], all=True)
|
||||||
self.assertNotLogged(*tests['ip4'], all=True)
|
self.assertNotLogged(*tests['ip4'], all=True)
|
||||||
# test unban ip6 :
|
# test unban ip6 :
|
||||||
logSys.debug('# === unban ipv6 ==='); self.pruneLog()
|
self.pruneLog('# === unban ipv6 ===')
|
||||||
action.unban({'ip': IPAddr('2001:DB8::')})
|
action.unban({'ip': IPAddr('2001:DB8::')})
|
||||||
self.assertLogged(*tests['ip6-check']+tests['ip6-unban'], all=True)
|
self.assertLogged(*tests['ip6-check']+tests['ip6-unban'], all=True)
|
||||||
self.assertNotLogged(*tests['ip4'], all=True)
|
self.assertNotLogged(*tests['ip4'], all=True)
|
||||||
# test stop :
|
# test stop :
|
||||||
logSys.debug('# === stop ==='); self.pruneLog()
|
self.pruneLog('# === stop ===')
|
||||||
action.stop()
|
action.stop()
|
||||||
self.assertLogged(*tests['stop'], all=True)
|
self.assertLogged(*tests['stop'], all=True)
|
||||||
|
|
||||||
|
def _executeMailCmd(self, realCmd, timeout=60):
|
||||||
|
# replace pipe to mail with pipe to cat:
|
||||||
|
realCmd = re.sub(r'\)\s*\|\s*mail\b([^\n]*)',
|
||||||
|
r' echo mail \1 ) | cat', realCmd)
|
||||||
|
# replace abuse retrieving (possible no-network):
|
||||||
|
realCmd = re.sub(r'[^\n]+\bADDRESSES=\$\(dig\s[^\n]+',
|
||||||
|
'ADDRESSES="abuse-1@abuse-test-server, abuse-2@abuse-test-server"', realCmd)
|
||||||
|
# execute action:
|
||||||
|
return _actions.CommandAction.executeCmd(realCmd, timeout=timeout)
|
||||||
|
|
||||||
|
def testComplexMailActionMultiLog(self):
|
||||||
|
testJailsActions = (
|
||||||
|
# mail-whois-lines --
|
||||||
|
('j-mail-whois-lines',
|
||||||
|
'mail-whois-lines['
|
||||||
|
'name=%(__name__)s, grepopts="-m 1", grepmax=2, mailcmd="mail -s", ' +
|
||||||
|
# 2 logs to test grep from multiple logs:
|
||||||
|
'logpath="' + os.path.join(TEST_FILES_DIR, "testcase01.log") + '\n' +
|
||||||
|
' ' + os.path.join(TEST_FILES_DIR, "testcase01a.log") + '", '
|
||||||
|
'_whois_command="echo \'-- information about <ip> --\'"'
|
||||||
|
']',
|
||||||
|
{
|
||||||
|
'ip4-ban': (
|
||||||
|
'The IP 87.142.124.10 has just been banned by Fail2Ban after',
|
||||||
|
'100 attempts against j-mail-whois-lines.',
|
||||||
|
'Here is more information about 87.142.124.10 :',
|
||||||
|
'-- information about 87.142.124.10 --',
|
||||||
|
'Lines containing failures of 87.142.124.10 (max 2)',
|
||||||
|
'testcase01.log:Dec 31 11:59:59 [sshd] error: PAM: Authentication failure for kevin from 87.142.124.10',
|
||||||
|
'testcase01a.log:Dec 31 11:55:01 [sshd] error: PAM: Authentication failure for test from 87.142.124.10',
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
# complain --
|
||||||
|
('j-complain-abuse',
|
||||||
|
'complain['
|
||||||
|
'name=%(__name__)s, grepopts="-m 1", grepmax=2, mailcmd="mail -s",' +
|
||||||
|
# 2 logs to test grep from multiple logs:
|
||||||
|
'logpath="' + os.path.join(TEST_FILES_DIR, "testcase01.log") + '\n' +
|
||||||
|
' ' + os.path.join(TEST_FILES_DIR, "testcase01a.log") + '", '
|
||||||
|
']',
|
||||||
|
{
|
||||||
|
'ip4-ban': (
|
||||||
|
'Lines containing failures of 87.142.124.10 (max 2)',
|
||||||
|
'testcase01.log:Dec 31 11:59:59 [sshd] error: PAM: Authentication failure for kevin from 87.142.124.10',
|
||||||
|
'testcase01a.log:Dec 31 11:55:01 [sshd] error: PAM: Authentication failure for test from 87.142.124.10',
|
||||||
|
# both abuse mails should be separated with space:
|
||||||
|
'mail -s Abuse from 87.142.124.10 abuse-1@abuse-test-server abuse-2@abuse-test-server',
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
server = TestServer()
|
||||||
|
transm = server._Server__transm
|
||||||
|
cmdHandler = transm._Transmitter__commandHandler
|
||||||
|
|
||||||
|
for jail, act, tests in testJailsActions:
|
||||||
|
stream = self.getDefaultJailStream(jail, act)
|
||||||
|
|
||||||
|
# for cmd in stream:
|
||||||
|
# print(cmd)
|
||||||
|
|
||||||
|
# transmit jail to the server:
|
||||||
|
for cmd in stream:
|
||||||
|
# command to server:
|
||||||
|
ret, res = transm.proceed(cmd)
|
||||||
|
self.assertEqual(ret, 0)
|
||||||
|
|
||||||
|
jails = server._Server__jails
|
||||||
|
|
||||||
|
for jail, act, tests in testJailsActions:
|
||||||
|
# print(jail, jails[jail])
|
||||||
|
for a in jails[jail].actions:
|
||||||
|
action = jails[jail].actions[a]
|
||||||
|
logSys.debug('# ' + ('=' * 50))
|
||||||
|
logSys.debug('# == %-44s ==', jail + ' - ' + action._name)
|
||||||
|
logSys.debug('# ' + ('=' * 50))
|
||||||
|
# wrap default command processor:
|
||||||
|
action.executeCmd = self._executeMailCmd
|
||||||
|
# test ban :
|
||||||
|
self.pruneLog('# === ban ===')
|
||||||
|
action.ban({'ip': IPAddr('87.142.124.10'),
|
||||||
|
'failures': 100,
|
||||||
|
})
|
||||||
|
self.assertLogged(*tests['ip4-ban'], all=True)
|
||||||
|
|
|
@ -5,12 +5,16 @@ After=network.target iptables.service firewalld.service
|
||||||
PartOf=iptables.service firewalld.service
|
PartOf=iptables.service firewalld.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=forking
|
Type=simple
|
||||||
ExecStart=/usr/bin/fail2ban-client -x start
|
ExecStartPre=/bin/mkdir -p /var/run/fail2ban
|
||||||
|
ExecStart=/usr/bin/fail2ban-server -xf start
|
||||||
|
# if should be logged in systemd journal, use following line or set logtarget to stdout in fail2ban.local
|
||||||
|
# ExecStart=/usr/bin/fail2ban-server -xf --logtarget=stdout start
|
||||||
ExecStop=/usr/bin/fail2ban-client stop
|
ExecStop=/usr/bin/fail2ban-client stop
|
||||||
ExecReload=/usr/bin/fail2ban-client reload
|
ExecReload=/usr/bin/fail2ban-client reload
|
||||||
PIDFile=/var/run/fail2ban/fail2ban.pid
|
PIDFile=/var/run/fail2ban/fail2ban.pid
|
||||||
Restart=always
|
Restart=on-failure
|
||||||
|
RestartPreventExitStatus=0 255
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|
|
@ -23,6 +23,9 @@ pidfile path
|
||||||
logging level
|
logging level
|
||||||
.HP
|
.HP
|
||||||
\fB\-\-logtarget\fR <FILE>|STDOUT|STDERR|SYSLOG
|
\fB\-\-logtarget\fR <FILE>|STDOUT|STDERR|SYSLOG
|
||||||
|
logging target
|
||||||
|
.br
|
||||||
|
Note. If fail2ban running as systemd-service, for logging to the systemd-journal, the logtarget could be set to STDOUT
|
||||||
.HP
|
.HP
|
||||||
\fB\-\-syslogsocket\fR auto|<FILE>
|
\fB\-\-syslogsocket\fR auto|<FILE>
|
||||||
.TP
|
.TP
|
||||||
|
|
|
@ -130,7 +130,9 @@ The items that can be set are:
|
||||||
verbosity level of log output: CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, TRACEDEBUG, HEAVYDEBUG or corresponding numeric value (50-5). Default: ERROR (equal 40)
|
verbosity level of log output: CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, TRACEDEBUG, HEAVYDEBUG or corresponding numeric value (50-5). Default: ERROR (equal 40)
|
||||||
.TP
|
.TP
|
||||||
.B logtarget
|
.B logtarget
|
||||||
log target: filename, SYSLOG, STDERR or STDOUT. Default: STDERR
|
log target: filename, SYSLOG, STDERR or STDOUT. Default: STDOUT if not set in fail2ban.conf/fail2ban.local
|
||||||
|
.br
|
||||||
|
Note. If fail2ban running as systemd-service, for logging to the systemd-journal, the logtarget could be set to STDOUT
|
||||||
.br
|
.br
|
||||||
Only a single log target can be specified.
|
Only a single log target can be specified.
|
||||||
If you change logtarget from the default value and you are using logrotate -- also adjust or disable rotation in the
|
If you change logtarget from the default value and you are using logrotate -- also adjust or disable rotation in the
|
||||||
|
|
Loading…
Reference in New Issue