mirror of https://github.com/fail2ban/fail2ban
MRG: from 0.9
commit
9e087b508d
21
ChangeLog
21
ChangeLog
|
@ -54,10 +54,12 @@ configuration before relying on it.
|
|||
into logging messages in case of error or at DEBUG loglevel.
|
||||
* Added action xarf-login-attack to report formatted attack messages
|
||||
according to the XARF standard (v0.2). Close gh-105
|
||||
* Add filter for apache-modsecurity
|
||||
* Support PyPy
|
||||
* Filter for stunnel
|
||||
|
||||
- Enhancements
|
||||
* Jail names increased to 26 characters and iptables prefix reduced
|
||||
from fail2ban- to f2b- as suggested by buanzo in gh-462.
|
||||
* Multiline filter for sendmail-spam. Close gh-418
|
||||
* Multiline regex for Disconnecting: Too many authentication failures for
|
||||
root [preauth]\nConnection closed by 6X.XXX.XXX.XXX [preauth]
|
||||
|
@ -90,8 +92,11 @@ ver. 0.8.12 (2013/12/XX) - things-can-only-get-better
|
|||
- Fix firewall-cmd actioncheck - patch from Adam Tkac. Redhat Bug #979622
|
||||
- Fix apache-common for apache-2.4 log file format. Thanks Mark White.
|
||||
Closes gh-516
|
||||
- Asynchat changed to use push method which verifys whether all data was
|
||||
send. This ensures that all data is sent before closing the connection.
|
||||
|
||||
- Enhancements:
|
||||
- added firewallcmd-ipset action
|
||||
- long names on jails documented based on iptables limit of 30 less
|
||||
len("fail2ban-").
|
||||
- remove indentation of name and loglevel while logging to SYSLOG to
|
||||
|
@ -100,14 +105,18 @@ ver. 0.8.12 (2013/12/XX) - things-can-only-get-better
|
|||
- updated check_fail2ban to return performance data for all jails.
|
||||
- filter apache-noscript now includes php cgi scripts.
|
||||
Thanks dani. Closes gh-503
|
||||
- added ufw action. Thanks Guilhem Lettron. lp-#701522
|
||||
- exim-spam filter to match spamassassin log entry for option SAdevnull.
|
||||
Thanks Ivo Truxa. Closes gh-533
|
||||
- filter.d/nsd.conf -- also amended Unix date template to match nsd format
|
||||
- loglines now also report "[PID]" after the name portion
|
||||
|
||||
- New Features:
|
||||
|
||||
* filter.d/solid-pop3d -- added thanks to Jacques Lav!gnotte on mailinglist.
|
||||
* filter.d/nsd.conf -- also amended Unix date template to match nsd format
|
||||
|
||||
- Enhancements:
|
||||
- loglines now also report "[PID]" after the name portion
|
||||
- Added filter for solid-pop3d -- thanks to Jacques Lav!gnotte on mailinglist.
|
||||
- Added filter for apache-modsecurity
|
||||
- Added filter for openwebmail thanks Ivo Truxa. Closes gh-543
|
||||
- Added filter for horde
|
||||
|
||||
ver. 0.8.11 (2013/11/13) - loves-unittests-and-tight-DoS-free-filter-regexes
|
||||
|
||||
|
|
460
DEVELOP
460
DEVELOP
|
@ -34,465 +34,7 @@ When submitting pull requests on GitHub we ask you to:
|
|||
* Include a change to the relevant section of the ChangeLog; and
|
||||
* Include yourself in THANKS if not already there.
|
||||
|
||||
Filters
|
||||
=======
|
||||
|
||||
Filters are tricky. They need to:
|
||||
* work with a variety of the versions of the software that generates the logs;
|
||||
* work with the range of logging configuration options available in the
|
||||
software;
|
||||
* work with multiple operating systems;
|
||||
* not make assumptions about the log format in excess of the software
|
||||
(e.g. do not assume a username doesn't contain spaces and use \S+ unless
|
||||
you've checked the source code);
|
||||
* account for how future versions of the software will log messages
|
||||
(e.g. guess what would happen to the log message if different authentication
|
||||
types are added);
|
||||
* not be susceptible to DoS vulnerabilities (see Filter Security below); and
|
||||
* match intended log lines only.
|
||||
|
||||
Please follow the steps from Filter Test Cases to Developing Filter Regular
|
||||
Expressions and submit a GitHub pull request (PR) afterwards. If you get stuck,
|
||||
you can push your unfinished changes and still submit a PR -- describe
|
||||
what you have done, what is the hurdle, and we'll attempt to help (PR
|
||||
will be automagically updated with future commits you would push to
|
||||
complete it).
|
||||
|
||||
Filter test cases
|
||||
-----------------
|
||||
|
||||
Purpose:
|
||||
|
||||
Start by finding the log messages that the application generates related to
|
||||
some form of authentication failure. If you are adding to an existing filter
|
||||
think about whether the log messages are of a similar importance and purpose
|
||||
to the existing filter. If you were a user of Fail2Ban, and did a package
|
||||
update of Fail2Ban that started matching new log messages, would anything
|
||||
unexpected happen? Would the bantime/findtime for the jail be appropriate for
|
||||
the new log messages? If it doesn't, perhaps it needs to be in a separate
|
||||
filter definition, for example like exim filter aims at authentication failures
|
||||
and exim-spam at log messages related to spam.
|
||||
|
||||
Even if it is a new filter you may consider separating the log messages into
|
||||
different filters based on purpose.
|
||||
|
||||
Cause:
|
||||
|
||||
Are some of the log lines a result of the same action? For example, is a PAM
|
||||
failure log message, followed by an application specific failure message the
|
||||
result of the same user/script action? If you add regular expressions for
|
||||
both you would end up with two failures for a single action.
|
||||
Therefore, select the most appropriate log message and document the other log
|
||||
message) with a test case not to match it and a description as to why you chose
|
||||
one over another.
|
||||
|
||||
With the selected log lines consider what action has caused those log
|
||||
messages and whether they could have been generated by accident? Could
|
||||
the log message be occurring due to the first step towards the application
|
||||
asking for authentication? Could the log messages occur often? If some of
|
||||
these are true make a note of this in the jail.conf example that you provide.
|
||||
|
||||
Samples:
|
||||
|
||||
It is important to include log file samples so any future change in the regular
|
||||
expression will still work with the log lines you have identified.
|
||||
|
||||
The sample log messages are provided in a file under testcases/files/logs/
|
||||
named identically as the corresponding filter (but without .conf extension).
|
||||
Each log line should be preceded by a line with failJSON metadata (so the logs
|
||||
lines are tested in the test suite) directly above the log line. If there is
|
||||
any specific information about the log message, such as version or an
|
||||
application configuration option that is needed for the message to occur,
|
||||
include this in a comment (line beginning with #) above the failJSON metadata.
|
||||
|
||||
Log samples should include only one, definitely not more than 3, examples of
|
||||
log messages of the same form. If log messages are different in different
|
||||
versions of the application log messages that show this are encouraged.
|
||||
|
||||
Also attempt to inject an IP into the application (e.g. by specifying
|
||||
it as a username) so that Fail2Ban possibly detects the IP
|
||||
from user input rather than the true origin. See the Filter Security section
|
||||
and the top example in testcases/files/logs/apache-auth as to how to do this.
|
||||
One you have discovered that this is possible, correct the regex so it doesn't
|
||||
match and provide this as a test case with "match": false (see failJSON below).
|
||||
|
||||
If the mechanism to create the log message isn't obvious provide a
|
||||
configuration and/or sample scripts testcases/files/config/{filtername} and
|
||||
reference these in the comments above the log line.
|
||||
|
||||
FailJSON metadata:
|
||||
|
||||
A failJSON metadata is a comment immediately above the log message. It will
|
||||
look like:
|
||||
|
||||
# failJSON: { "time": "2013-06-10T10:10:59", "match": true , "host": "93.184.216.119" }
|
||||
|
||||
Time should match the time of the log message. It is in a specific format of
|
||||
Year-Month-Day'T'Hour:minute:Second. If your log message does not include a
|
||||
year, like the example below, the year should be listed as 2005, if before Sun
|
||||
Aug 14 10am UTC, and 2004 if afterwards. Here is an example failJSON
|
||||
line preceding a sample log line:
|
||||
|
||||
# failJSON: { "time": "2005-03-24T15:25:51", "match": true , "host": "198.51.100.87" }
|
||||
Mar 24 15:25:51 buffalo1 dropbear[4092]: bad password attempt for 'root' from 198.51.100.87:5543
|
||||
|
||||
The "host" in failJSON should contain the IP or domain that should be blocked.
|
||||
|
||||
For long lines that you do not want to be matched (e.g. from log injection
|
||||
attacks) and any log lines to be excluded (see "Cause" section above), set
|
||||
"match": false in the failJSON and describe the reason in the comment above.
|
||||
|
||||
After developing regexes, the following command will test all failJSON metadata
|
||||
against the log lines in all sample log files
|
||||
|
||||
./fail2ban-testcases testSampleRegex
|
||||
|
||||
Developing Filter Regular Expressions
|
||||
-------------------------------------
|
||||
|
||||
Date/Time:
|
||||
|
||||
At the moment, Fail2Ban depends on log lines to have time stamps. That is why
|
||||
before starting to develop failregex, check if your log line format known to
|
||||
Fail2Ban. Copy the time component from the log line and append an IP address to
|
||||
test with following command:
|
||||
|
||||
./fail2ban-regex "2013-09-19 02:46:12 1.2.3.4" "<HOST>"
|
||||
|
||||
Output of such command should contain something like:
|
||||
|
||||
Date template hits:
|
||||
|- [# of hits] date format
|
||||
| [1] Year-Month-Day Hour:Minute:Second
|
||||
|
||||
Ensure that the template description matches time/date elements in your log line
|
||||
time stamp. If there is no matched format then date template needs to be added
|
||||
to server/datedetector.py. Ensure that a new template is added in the order
|
||||
that more specific matches occur first and that there is no confusion between a
|
||||
Day and a Month.
|
||||
|
||||
Filter file:
|
||||
|
||||
The filter is specified in a config/filter.d/{filtername}.conf file. Filter file
|
||||
can have sections INCLUDES (optional) and Definition as follows:
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
before = common.conf
|
||||
|
||||
after = filtername.local
|
||||
|
||||
[Definition]
|
||||
|
||||
failregex = ....
|
||||
|
||||
ignoreregex = ....
|
||||
|
||||
This is also documented in the man page jail.conf (section 5). Other definitions
|
||||
can be added to make failregex's more readable and maintainable to be used
|
||||
through string Interpolations (see http://docs.python.org/2.7/library/configparser.html)
|
||||
|
||||
|
||||
General rules:
|
||||
|
||||
Use "before" if you need to include a common set of rules, like syslog or if
|
||||
there is a common set of regexes for multiple filters.
|
||||
|
||||
Use "after" if you wish to allow the user to overwrite a set of customisations
|
||||
of the current filter. This file doesn't need to exist.
|
||||
|
||||
Try to avoid using ignoreregex mainly for performance reasons. The case when you
|
||||
would use it is if in trying to avoid using it, you end up with an unreadable
|
||||
failregex.
|
||||
|
||||
Syslog:
|
||||
|
||||
If your application logs to syslog you can take advantage of log line prefix
|
||||
definitions present in common.conf. So as a base use:
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
before = common.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
_daemon = app
|
||||
|
||||
failregex = ^%(__prefix_line)s
|
||||
|
||||
In this example common.conf defines __prefix_line which also contains the
|
||||
_daemon name (in syslog terms the service) you have just specified. _daemon
|
||||
can also be a regex.
|
||||
|
||||
For example, to capture following line _daemon should be set to "dovecot"
|
||||
|
||||
Dec 12 11:19:11 dunnart dovecot: pop3-login: Aborted login (tried to use disabled plaintext auth): rip=190.210.136.21, lip=113.212.99.193
|
||||
|
||||
and then ^%(__prefix_line)s would match "Dec 12 11:19:11 dunnart dovecot:
|
||||
". Note it matches the trailing space(s) as well.
|
||||
|
||||
Substitutions (AKA string interpolations):
|
||||
|
||||
We have used string interpolations in above examples. They are useful for
|
||||
making the regexes more readable, reuse generic patterns in multiple failregex
|
||||
lines, and also to refer definition of regex parts to specific filters or even
|
||||
to the user. General principle is that value of a _name variable replaces
|
||||
occurrences of %(_name)s within the same section or anywhere in the config file
|
||||
if defined in [DEFAULT] section.
|
||||
|
||||
Regular Expressions:
|
||||
|
||||
Regular expressions (failregex, ignoreregex) assume that the date/time has been
|
||||
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
|
||||
the < at the start so regex should be similar to '^<> <HOST> is evil$' using
|
||||
<HOST> where the IP/domain name appears in the log line.
|
||||
|
||||
The following general rules apply to regular expressions:
|
||||
|
||||
* ensure regexes start with a ^ and are as restrictive as possible. E.g. do not
|
||||
use .* if \d+ is sufficient;
|
||||
* use functionality of Python regexes defined in the standard Python re library
|
||||
http://docs.python.org/2/library/re.html;
|
||||
* make regular expressions readable (as much as possible). E.g.
|
||||
(?:...) represents a non-capturing regex but (...) is more readable, thus
|
||||
preferred.
|
||||
|
||||
If you have only a basic knowledge of regular repressions we advise to read
|
||||
http://docs.python.org/2/library/re.html first. It doesn't take long and would
|
||||
remind you e.g. which characters you need to escape and which you don't.
|
||||
|
||||
Developing/testing a regex:
|
||||
|
||||
You can develop a regex in a file or using command line depending on your
|
||||
preference. You can also use samples you have already created in the test cases
|
||||
or test them one at a time.
|
||||
|
||||
The general tool for testing Fail2Ban regexes is fail2ban-regex. To see how to
|
||||
use it run:
|
||||
|
||||
./fail2ban-regex --help
|
||||
|
||||
Take note of -l heavydebug / -l debug and -v as they might be very useful.
|
||||
|
||||
TIP: Take a look at the source code of the application you are developing
|
||||
failregex for. You may see optional or extra log messages, or parts there
|
||||
of, that need to form part of your regex. It may also reveal how some
|
||||
parts are constrained and different formats depending on configuration or
|
||||
less common usages.
|
||||
|
||||
TIP: For looking through source code - http://sourcecodebrowser.com/ . It has
|
||||
call graphs and can browse different versions.
|
||||
|
||||
TIP: Some applications log spaces at the end. If you are not sure add \s*$ as
|
||||
the end part of the regex.
|
||||
|
||||
If your regex is not matching, http://www.debuggex.com/?flavor=python can help
|
||||
to tune it. fail2ban-regex -D ... will present Debuggex URLs for the regexs
|
||||
and sample log files that you pass into it.
|
||||
|
||||
In general use when using regex debuggers for generating fail2ban filters:
|
||||
* use regex from the ./fail2ban-regex output (to ensure all substitutions are
|
||||
done)
|
||||
* replace <HOST> with (?&.ipv4)
|
||||
* make sure that regex type set to Python
|
||||
* for the test data put your log output with the date/time removed
|
||||
|
||||
When you have fixed the regex put it back into your filter file.
|
||||
|
||||
Please spread the good word about Debuggex - Serge Toarca is kindly continuing
|
||||
its free availability to Open Source developers.
|
||||
|
||||
Finishing up:
|
||||
|
||||
If you've added a new filter, add a new entry in config/jail.conf. The theory
|
||||
here is that a user will create a jail.local with [filtername]\nenable=true to
|
||||
enable your jail.
|
||||
|
||||
So more specifically in the [filter] section in jail.conf:
|
||||
* ensure that you have "enabled = false" (users will enable as needed);
|
||||
* use "filter =" set to your filter name;
|
||||
* use a typical action to disable ports associated with the application;
|
||||
* set "logpath" to the usual location of application log file;
|
||||
* if the default findtime or bantime isn't appropriate to the filter, specify
|
||||
more appropriate choices (possibly with a brief comment line).
|
||||
|
||||
Submit github pull request (See "Pull Requests" above) for
|
||||
github.com/fail2ban/fail2ban containing your great work.
|
||||
|
||||
Filter Security
|
||||
---------------
|
||||
|
||||
Poor filter regular expressions are susceptible to DoS attacks.
|
||||
|
||||
When a remote user has the ability to introduce text that would match filter's
|
||||
failregex, while matching inserted text to the <HOST> part, they have the
|
||||
ability to deny any host they choose.
|
||||
|
||||
So the <HOST> part must be anchored on text generated by the application, and
|
||||
not the user, to an extent sufficient to prevent user inserting the entire text
|
||||
matching this or any other failregex.
|
||||
|
||||
Ideally filter regex should anchor at the beginning and at the end of log line.
|
||||
However as more applications log at the beginning than the end, anchoring the
|
||||
beginning is more important. If the log file used by the application is shared
|
||||
with other applications, like system logs, ensure the other application that use
|
||||
that log file do not log user generated text at the beginning of the line, or,
|
||||
if they do, ensure the regexes of the filter are sufficient to mitigate the risk
|
||||
of insertion.
|
||||
|
||||
|
||||
Examples of poor filters
|
||||
------------------------
|
||||
|
||||
1. Too restrictive
|
||||
|
||||
We find a log message:
|
||||
|
||||
Apr-07-13 07:08:36 Invalid command fial2ban from 1.2.3.4
|
||||
|
||||
We make a failregex
|
||||
|
||||
^Invalid command \S+ from <HOST>
|
||||
|
||||
Now think evil. The user does the command 'blah from 1.2.3.44'
|
||||
|
||||
The program diligently logs:
|
||||
|
||||
Apr-07-13 07:08:36 Invalid command blah from 1.2.3.44 from 1.2.3.4
|
||||
|
||||
And fail2ban matches 1.2.3.44 as the IP that it ban. A DoS attack was successful.
|
||||
|
||||
The fix here is that the command can be anything so .* is appropriate.
|
||||
|
||||
^Invalid command .* from <HOST>
|
||||
|
||||
Here the .* will match until the end of the string. Then realise it has more to
|
||||
match, i.e. "from <HOST>" and go back until it find this. Then it will ban
|
||||
1.2.3.4 correctly. Since the <HOST> is always at the end, end the regex with a $.
|
||||
|
||||
^Invalid command .* from <HOST>$
|
||||
|
||||
Note if we'd just had the expression:
|
||||
|
||||
^Invalid command \S+ from <HOST>$
|
||||
|
||||
Then provided the user put a space in their command they would have never been
|
||||
banned.
|
||||
|
||||
2. Unanchored regex can match other user injected data
|
||||
|
||||
From the Apache vulnerability CVE-2013-2178
|
||||
( original ref: https://vndh.net/note:fail2ban-089-denial-service ).
|
||||
|
||||
An example bad regex for Apache:
|
||||
|
||||
failregex = [[]client <HOST>[]] user .* not found
|
||||
|
||||
Since the user can do a get request on:
|
||||
|
||||
GET /[client%20192.168.0.1]%20user%20root%20not%20found HTTP/1.0
|
||||
Host: remote.site
|
||||
|
||||
Now the log line will be:
|
||||
|
||||
[Sat Jun 01 02:17:42 2013] [error] [client 192.168.33.1] File does not exist: /srv/http/site/[client 192.168.0.1] user root not found
|
||||
|
||||
As this log line doesn't match other expressions hence it matches the above
|
||||
regex and blocks 192.168.33.1 as a denial of service from the HTTP requester.
|
||||
|
||||
3. Over greedy pattern matching
|
||||
|
||||
From: https://github.com/fail2ban/fail2ban/pull/426
|
||||
|
||||
An example ssh log (simplified)
|
||||
|
||||
Sep 29 17:15:02 spaceman sshd[12946]: Failed password for user from 127.0.0.1 port 20000 ssh1: ruser remoteuser
|
||||
|
||||
As we assume username can include anything including spaces its prudent to put
|
||||
.* here. The remote user can also exist as anything so lets not make assumptions again.
|
||||
|
||||
failregex = ^%(__prefix_line)sFailed \S+ for .* from <HOST>( port \d*)?( ssh\d+)?(: ruser .*)?$
|
||||
|
||||
So this works. The problem is if the .* after remote user is injected by the
|
||||
user to be 'from 1.2.3.4'. The resultant log line is.
|
||||
|
||||
Sep 29 17:15:02 spaceman sshd[12946]: Failed password for user from 127.0.0.1 port 20000 ssh1: ruser from 1.2.3.4
|
||||
|
||||
Testing with:
|
||||
|
||||
fail2ban-regex -v 'Sep 29 17:15:02 Failed password for user from 127.0.0.1 port 20000 ssh1: ruser from 1.2.3.4' '^ Failed \S+ for .* from <HOST>( port \d*)?( ssh\d+)?(: ruser .*)?$'
|
||||
|
||||
TIP: I've removed the bit that matches __prefix_line from the regex and log.
|
||||
|
||||
Shows:
|
||||
|
||||
1) [1] ^ Failed \S+ for .* from <HOST>( port \d*)?( ssh\d+)?(: ruser .*)?$
|
||||
1.2.3.4 Sun Sep 29 17:15:02 2013
|
||||
|
||||
It should of matched 127.0.0.1. So the first greedy part of the greedy regex
|
||||
matched until the end of the string. The was no "from <HOST>" so the regex
|
||||
engine worked backwards from the end of the string until this was matched.
|
||||
|
||||
The result was that 1.2.3.4 was matched, injected by the user, and the wrong IP
|
||||
was banned.
|
||||
|
||||
The solution here is to make the first .* non-greedy with .*?. Here it matches
|
||||
as little as required and the fail2ban-regex tool shows the output:
|
||||
|
||||
fail2ban-regex -v 'Sep 29 17:15:02 Failed password for user from 127.0.0.1 port 20000 ssh1: ruser from 1.2.3.4' '^ Failed \S+ for .*? from <HOST>( port \d*)?( ssh\d+)?(: ruser .*)?$'
|
||||
|
||||
1) [1] ^ Failed \S+ for .*? from <HOST>( port \d*)?( ssh\d+)?(: ruser .*)?$
|
||||
127.0.0.1 Sun Sep 29 17:15:02 2013
|
||||
|
||||
So the general case here is a log line that contains:
|
||||
|
||||
(fixed_data_1)<HOST>(fixed_data_2)(user_injectable_data)
|
||||
|
||||
Where the regex that matches fixed_data_1 is gready and matches the entire
|
||||
string, before moving backwards and user_injectable_data can match the entire
|
||||
string.
|
||||
|
||||
Another case:
|
||||
|
||||
ref: https://www.debuggex.com/r/CtAbeKMa2sDBEfA2/0
|
||||
|
||||
A webserver logs the following without URL escaping:
|
||||
|
||||
[error] 2865#0: *66647 user "xyz" was not found in "/file", client: 1.2.3.1, server: www.host.com, request: "GET ", client: 3.2.1.1, server: fake.com, request: "GET exploited HTTP/3.3", host: "injected.host", host: "www.myhost.com"
|
||||
|
||||
regex:
|
||||
|
||||
failregex = ^ \[error\] \d+#\d+: \*\d+ user "\S+":? (?:password mismatch|was not found in ".*"), client: <HOST>, server: \S+, request: "\S+ .+ HTTP/\d+\.\d+", host: "\S+"
|
||||
|
||||
The .* matches to the end of the string. Finds that it can't continue to match
|
||||
", client ... so it moves from the back and find that the user injected web URL:
|
||||
|
||||
", client: 3.2.1.1, server: fake.com, request: "GET exploited HTTP/3.3", host: "injected.host
|
||||
|
||||
In this case there is a fixed host: "www.myhost.com" at the end so the solution
|
||||
is to anchor the regex at the end with a $.
|
||||
|
||||
If this wasn't the case then first .* needed to be made so it didn't capture
|
||||
beyond <HOST>.
|
||||
|
||||
4. Application generates two identical log messages with different meanings
|
||||
|
||||
If the application generates the following two messages under different
|
||||
circumstances:
|
||||
|
||||
client <IP>: authentication failed
|
||||
client <USER>: authentication failed
|
||||
|
||||
|
||||
Then it's obvious that a regex of "^client <HOST>: authentication
|
||||
failed$" will still cause problems if the user can trigger the second
|
||||
log message with a <USER> of 123.1.1.1.
|
||||
|
||||
Here there's nothing to do except request/change the application so it logs
|
||||
messages differently.
|
||||
|
||||
If you are developing filters see the FILTERS file for documentation.
|
||||
|
||||
Code Testing
|
||||
============
|
||||
|
|
|
@ -0,0 +1,469 @@
|
|||
__ _ _ ___ _
|
||||
/ _|__ _(_) |_ ) |__ __ _ _ _
|
||||
| _/ _` | | |/ /| '_ \/ _` | ' \
|
||||
|_| \__,_|_|_/___|_.__/\__,_|_||_|
|
||||
|
||||
================================================================================
|
||||
Developing Filters
|
||||
================================================================================
|
||||
|
||||
Filters
|
||||
=======
|
||||
|
||||
Filters are tricky. They need to:
|
||||
* work with a variety of the versions of the software that generates the logs;
|
||||
* work with the range of logging configuration options available in the
|
||||
software;
|
||||
* work with multiple operating systems;
|
||||
* not make assumptions about the log format in excess of the software
|
||||
(e.g. do not assume a username doesn't contain spaces and use \S+ unless
|
||||
you've checked the source code);
|
||||
* account for how future versions of the software will log messages
|
||||
(e.g. guess what would happen to the log message if different authentication
|
||||
types are added);
|
||||
* not be susceptible to DoS vulnerabilities (see Filter Security below); and
|
||||
* match intended log lines only.
|
||||
|
||||
Please follow the steps from Filter Test Cases to Developing Filter Regular
|
||||
Expressions and submit a GitHub pull request (PR) afterwards. If you get stuck,
|
||||
you can push your unfinished changes and still submit a PR -- describe
|
||||
what you have done, what is the hurdle, and we'll attempt to help (PR
|
||||
will be automagically updated with future commits you would push to
|
||||
complete it).
|
||||
|
||||
Filter test cases
|
||||
-----------------
|
||||
|
||||
Purpose:
|
||||
|
||||
Start by finding the log messages that the application generates related to
|
||||
some form of authentication failure. If you are adding to an existing filter
|
||||
think about whether the log messages are of a similar importance and purpose
|
||||
to the existing filter. If you were a user of Fail2Ban, and did a package
|
||||
update of Fail2Ban that started matching new log messages, would anything
|
||||
unexpected happen? Would the bantime/findtime for the jail be appropriate for
|
||||
the new log messages? If it doesn't, perhaps it needs to be in a separate
|
||||
filter definition, for example like exim filter aims at authentication failures
|
||||
and exim-spam at log messages related to spam.
|
||||
|
||||
Even if it is a new filter you may consider separating the log messages into
|
||||
different filters based on purpose.
|
||||
|
||||
Cause:
|
||||
|
||||
Are some of the log lines a result of the same action? For example, is a PAM
|
||||
failure log message, followed by an application specific failure message the
|
||||
result of the same user/script action? If you add regular expressions for
|
||||
both you would end up with two failures for a single action.
|
||||
Therefore, select the most appropriate log message and document the other log
|
||||
message) with a test case not to match it and a description as to why you chose
|
||||
one over another.
|
||||
|
||||
With the selected log lines consider what action has caused those log
|
||||
messages and whether they could have been generated by accident? Could
|
||||
the log message be occurring due to the first step towards the application
|
||||
asking for authentication? Could the log messages occur often? If some of
|
||||
these are true make a note of this in the jail.conf example that you provide.
|
||||
|
||||
Samples:
|
||||
|
||||
It is important to include log file samples so any future change in the regular
|
||||
expression will still work with the log lines you have identified.
|
||||
|
||||
The sample log messages are provided in a file under testcases/files/logs/
|
||||
named identically as the corresponding filter (but without .conf extension).
|
||||
Each log line should be preceded by a line with failJSON metadata (so the logs
|
||||
lines are tested in the test suite) directly above the log line. If there is
|
||||
any specific information about the log message, such as version or an
|
||||
application configuration option that is needed for the message to occur,
|
||||
include this in a comment (line beginning with #) above the failJSON metadata.
|
||||
|
||||
Log samples should include only one, definitely not more than 3, examples of
|
||||
log messages of the same form. If log messages are different in different
|
||||
versions of the application log messages that show this are encouraged.
|
||||
|
||||
Also attempt to inject an IP into the application (e.g. by specifying
|
||||
it as a username) so that Fail2Ban possibly detects the IP
|
||||
from user input rather than the true origin. See the Filter Security section
|
||||
and the top example in testcases/files/logs/apache-auth as to how to do this.
|
||||
One you have discovered that this is possible, correct the regex so it doesn't
|
||||
match and provide this as a test case with "match": false (see failJSON below).
|
||||
|
||||
If the mechanism to create the log message isn't obvious provide a
|
||||
configuration and/or sample scripts testcases/files/config/{filtername} and
|
||||
reference these in the comments above the log line.
|
||||
|
||||
FailJSON metadata:
|
||||
|
||||
A failJSON metadata is a comment immediately above the log message. It will
|
||||
look like:
|
||||
|
||||
# failJSON: { "time": "2013-06-10T10:10:59", "match": true , "host": "93.184.216.119" }
|
||||
|
||||
Time should match the time of the log message. It is in a specific format of
|
||||
Year-Month-Day'T'Hour:minute:Second. If your log message does not include a
|
||||
year, like the example below, the year should be listed as 2005, if before Sun
|
||||
Aug 14 10am UTC, and 2004 if afterwards. Here is an example failJSON
|
||||
line preceding a sample log line:
|
||||
|
||||
# failJSON: { "time": "2005-03-24T15:25:51", "match": true , "host": "198.51.100.87" }
|
||||
Mar 24 15:25:51 buffalo1 dropbear[4092]: bad password attempt for 'root' from 198.51.100.87:5543
|
||||
|
||||
The "host" in failJSON should contain the IP or domain that should be blocked.
|
||||
|
||||
For long lines that you do not want to be matched (e.g. from log injection
|
||||
attacks) and any log lines to be excluded (see "Cause" section above), set
|
||||
"match": false in the failJSON and describe the reason in the comment above.
|
||||
|
||||
After developing regexes, the following command will test all failJSON metadata
|
||||
against the log lines in all sample log files
|
||||
|
||||
./fail2ban-testcases testSampleRegex
|
||||
|
||||
Developing Filter Regular Expressions
|
||||
-------------------------------------
|
||||
|
||||
Date/Time:
|
||||
|
||||
At the moment, Fail2Ban depends on log lines to have time stamps. That is why
|
||||
before starting to develop failregex, check if your log line format known to
|
||||
Fail2Ban. Copy the time component from the log line and append an IP address to
|
||||
test with following command:
|
||||
|
||||
./fail2ban-regex "2013-09-19 02:46:12 1.2.3.4" "<HOST>"
|
||||
|
||||
Output of such command should contain something like:
|
||||
|
||||
Date template hits:
|
||||
|- [# of hits] date format
|
||||
| [1] Year-Month-Day Hour:Minute:Second
|
||||
|
||||
Ensure that the template description matches time/date elements in your log line
|
||||
time stamp. If there is no matched format then date template needs to be added
|
||||
to server/datedetector.py. Ensure that a new template is added in the order
|
||||
that more specific matches occur first and that there is no confusion between a
|
||||
Day and a Month.
|
||||
|
||||
Filter file:
|
||||
|
||||
The filter is specified in a config/filter.d/{filtername}.conf file. Filter file
|
||||
can have sections INCLUDES (optional) and Definition as follows:
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
before = common.conf
|
||||
|
||||
after = filtername.local
|
||||
|
||||
[Definition]
|
||||
|
||||
failregex = ....
|
||||
|
||||
ignoreregex = ....
|
||||
|
||||
This is also documented in the man page jail.conf (section 5). Other definitions
|
||||
can be added to make failregex's more readable and maintainable to be used
|
||||
through string Interpolations (see http://docs.python.org/2.7/library/configparser.html)
|
||||
|
||||
|
||||
General rules:
|
||||
|
||||
Use "before" if you need to include a common set of rules, like syslog or if
|
||||
there is a common set of regexes for multiple filters.
|
||||
|
||||
Use "after" if you wish to allow the user to overwrite a set of customisations
|
||||
of the current filter. This file doesn't need to exist.
|
||||
|
||||
Try to avoid using ignoreregex mainly for performance reasons. The case when you
|
||||
would use it is if in trying to avoid using it, you end up with an unreadable
|
||||
failregex.
|
||||
|
||||
Syslog:
|
||||
|
||||
If your application logs to syslog you can take advantage of log line prefix
|
||||
definitions present in common.conf. So as a base use:
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
before = common.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
_daemon = app
|
||||
|
||||
failregex = ^%(__prefix_line)s
|
||||
|
||||
In this example common.conf defines __prefix_line which also contains the
|
||||
_daemon name (in syslog terms the service) you have just specified. _daemon
|
||||
can also be a regex.
|
||||
|
||||
For example, to capture following line _daemon should be set to "dovecot"
|
||||
|
||||
Dec 12 11:19:11 dunnart dovecot: pop3-login: Aborted login (tried to use disabled plaintext auth): rip=190.210.136.21, lip=113.212.99.193
|
||||
|
||||
and then ^%(__prefix_line)s would match "Dec 12 11:19:11 dunnart dovecot:
|
||||
". Note it matches the trailing space(s) as well.
|
||||
|
||||
Substitutions (AKA string interpolations):
|
||||
|
||||
We have used string interpolations in above examples. They are useful for
|
||||
making the regexes more readable, reuse generic patterns in multiple failregex
|
||||
lines, and also to refer definition of regex parts to specific filters or even
|
||||
to the user. General principle is that value of a _name variable replaces
|
||||
occurrences of %(_name)s within the same section or anywhere in the config file
|
||||
if defined in [DEFAULT] section.
|
||||
|
||||
Regular Expressions:
|
||||
|
||||
Regular expressions (failregex, ignoreregex) assume that the date/time has been
|
||||
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
|
||||
the < at the start so regex should be similar to '^<> <HOST> is evil$' using
|
||||
<HOST> where the IP/domain name appears in the log line.
|
||||
|
||||
The following general rules apply to regular expressions:
|
||||
|
||||
* ensure regexes start with a ^ and are as restrictive as possible. E.g. do not
|
||||
use .* if \d+ is sufficient;
|
||||
* use functionality of Python regexes defined in the standard Python re library
|
||||
http://docs.python.org/2/library/re.html;
|
||||
* make regular expressions readable (as much as possible). E.g.
|
||||
(?:...) represents a non-capturing regex but (...) is more readable, thus
|
||||
preferred.
|
||||
|
||||
If you have only a basic knowledge of regular repressions we advise to read
|
||||
http://docs.python.org/2/library/re.html first. It doesn't take long and would
|
||||
remind you e.g. which characters you need to escape and which you don't.
|
||||
|
||||
Developing/testing a regex:
|
||||
|
||||
You can develop a regex in a file or using command line depending on your
|
||||
preference. You can also use samples you have already created in the test cases
|
||||
or test them one at a time.
|
||||
|
||||
The general tool for testing Fail2Ban regexes is fail2ban-regex. To see how to
|
||||
use it run:
|
||||
|
||||
./fail2ban-regex --help
|
||||
|
||||
Take note of -l heavydebug / -l debug and -v as they might be very useful.
|
||||
|
||||
TIP: Take a look at the source code of the application you are developing
|
||||
failregex for. You may see optional or extra log messages, or parts there
|
||||
of, that need to form part of your regex. It may also reveal how some
|
||||
parts are constrained and different formats depending on configuration or
|
||||
less common usages.
|
||||
|
||||
TIP: For looking through source code - http://sourcecodebrowser.com/ . It has
|
||||
call graphs and can browse different versions.
|
||||
|
||||
TIP: Some applications log spaces at the end. If you are not sure add \s*$ as
|
||||
the end part of the regex.
|
||||
|
||||
If your regex is not matching, http://www.debuggex.com/?flavor=python can help
|
||||
to tune it. fail2ban-regex -D ... will present Debuggex URLs for the regexs
|
||||
and sample log files that you pass into it.
|
||||
|
||||
In general use when using regex debuggers for generating fail2ban filters:
|
||||
* use regex from the ./fail2ban-regex output (to ensure all substitutions are
|
||||
done)
|
||||
* replace <HOST> with (?&.ipv4)
|
||||
* make sure that regex type set to Python
|
||||
* for the test data put your log output with the date/time removed
|
||||
|
||||
When you have fixed the regex put it back into your filter file.
|
||||
|
||||
Please spread the good word about Debuggex - Serge Toarca is kindly continuing
|
||||
its free availability to Open Source developers.
|
||||
|
||||
Finishing up:
|
||||
|
||||
If you've added a new filter, add a new entry in config/jail.conf. The theory
|
||||
here is that a user will create a jail.local with [filtername]\nenable=true to
|
||||
enable your jail.
|
||||
|
||||
So more specifically in the [filter] section in jail.conf:
|
||||
* ensure that you have "enabled = false" (users will enable as needed);
|
||||
* use "filter =" set to your filter name;
|
||||
* use a typical action to disable ports associated with the application;
|
||||
* set "logpath" to the usual location of application log file;
|
||||
* if the default findtime or bantime isn't appropriate to the filter, specify
|
||||
more appropriate choices (possibly with a brief comment line).
|
||||
|
||||
Submit github pull request (See "Pull Requests" above) for
|
||||
github.com/fail2ban/fail2ban containing your great work.
|
||||
|
||||
Filter Security
|
||||
---------------
|
||||
|
||||
Poor filter regular expressions are susceptible to DoS attacks.
|
||||
|
||||
When a remote user has the ability to introduce text that would match filter's
|
||||
failregex, while matching inserted text to the <HOST> part, they have the
|
||||
ability to deny any host they choose.
|
||||
|
||||
So the <HOST> part must be anchored on text generated by the application, and
|
||||
not the user, to an extent sufficient to prevent user inserting the entire text
|
||||
matching this or any other failregex.
|
||||
|
||||
Ideally filter regex should anchor at the beginning and at the end of log line.
|
||||
However as more applications log at the beginning than the end, anchoring the
|
||||
beginning is more important. If the log file used by the application is shared
|
||||
with other applications, like system logs, ensure the other application that use
|
||||
that log file do not log user generated text at the beginning of the line, or,
|
||||
if they do, ensure the regexes of the filter are sufficient to mitigate the risk
|
||||
of insertion.
|
||||
|
||||
|
||||
Examples of poor filters
|
||||
------------------------
|
||||
|
||||
1. Too restrictive
|
||||
|
||||
We find a log message:
|
||||
|
||||
Apr-07-13 07:08:36 Invalid command fial2ban from 1.2.3.4
|
||||
|
||||
We make a failregex
|
||||
|
||||
^Invalid command \S+ from <HOST>
|
||||
|
||||
Now think evil. The user does the command 'blah from 1.2.3.44'
|
||||
|
||||
The program diligently logs:
|
||||
|
||||
Apr-07-13 07:08:36 Invalid command blah from 1.2.3.44 from 1.2.3.4
|
||||
|
||||
And fail2ban matches 1.2.3.44 as the IP that it ban. A DoS attack was successful.
|
||||
|
||||
The fix here is that the command can be anything so .* is appropriate.
|
||||
|
||||
^Invalid command .* from <HOST>
|
||||
|
||||
Here the .* will match until the end of the string. Then realise it has more to
|
||||
match, i.e. "from <HOST>" and go back until it find this. Then it will ban
|
||||
1.2.3.4 correctly. Since the <HOST> is always at the end, end the regex with a $.
|
||||
|
||||
^Invalid command .* from <HOST>$
|
||||
|
||||
Note if we'd just had the expression:
|
||||
|
||||
^Invalid command \S+ from <HOST>$
|
||||
|
||||
Then provided the user put a space in their command they would have never been
|
||||
banned.
|
||||
|
||||
2. Unanchored regex can match other user injected data
|
||||
|
||||
From the Apache vulnerability CVE-2013-2178
|
||||
( original ref: https://vndh.net/note:fail2ban-089-denial-service ).
|
||||
|
||||
An example bad regex for Apache:
|
||||
|
||||
failregex = [[]client <HOST>[]] user .* not found
|
||||
|
||||
Since the user can do a get request on:
|
||||
|
||||
GET /[client%20192.168.0.1]%20user%20root%20not%20found HTTP/1.0
|
||||
Host: remote.site
|
||||
|
||||
Now the log line will be:
|
||||
|
||||
[Sat Jun 01 02:17:42 2013] [error] [client 192.168.33.1] File does not exist: /srv/http/site/[client 192.168.0.1] user root not found
|
||||
|
||||
As this log line doesn't match other expressions hence it matches the above
|
||||
regex and blocks 192.168.33.1 as a denial of service from the HTTP requester.
|
||||
|
||||
3. Over greedy pattern matching
|
||||
|
||||
From: https://github.com/fail2ban/fail2ban/pull/426
|
||||
|
||||
An example ssh log (simplified)
|
||||
|
||||
Sep 29 17:15:02 spaceman sshd[12946]: Failed password for user from 127.0.0.1 port 20000 ssh1: ruser remoteuser
|
||||
|
||||
As we assume username can include anything including spaces its prudent to put
|
||||
.* here. The remote user can also exist as anything so lets not make assumptions again.
|
||||
|
||||
failregex = ^%(__prefix_line)sFailed \S+ for .* from <HOST>( port \d*)?( ssh\d+)?(: ruser .*)?$
|
||||
|
||||
So this works. The problem is if the .* after remote user is injected by the
|
||||
user to be 'from 1.2.3.4'. The resultant log line is.
|
||||
|
||||
Sep 29 17:15:02 spaceman sshd[12946]: Failed password for user from 127.0.0.1 port 20000 ssh1: ruser from 1.2.3.4
|
||||
|
||||
Testing with:
|
||||
|
||||
fail2ban-regex -v 'Sep 29 17:15:02 Failed password for user from 127.0.0.1 port 20000 ssh1: ruser from 1.2.3.4' '^ Failed \S+ for .* from <HOST>( port \d*)?( ssh\d+)?(: ruser .*)?$'
|
||||
|
||||
TIP: I've removed the bit that matches __prefix_line from the regex and log.
|
||||
|
||||
Shows:
|
||||
|
||||
1) [1] ^ Failed \S+ for .* from <HOST>( port \d*)?( ssh\d+)?(: ruser .*)?$
|
||||
1.2.3.4 Sun Sep 29 17:15:02 2013
|
||||
|
||||
It should of matched 127.0.0.1. So the first greedy part of the greedy regex
|
||||
matched until the end of the string. The was no "from <HOST>" so the regex
|
||||
engine worked backwards from the end of the string until this was matched.
|
||||
|
||||
The result was that 1.2.3.4 was matched, injected by the user, and the wrong IP
|
||||
was banned.
|
||||
|
||||
The solution here is to make the first .* non-greedy with .*?. Here it matches
|
||||
as little as required and the fail2ban-regex tool shows the output:
|
||||
|
||||
fail2ban-regex -v 'Sep 29 17:15:02 Failed password for user from 127.0.0.1 port 20000 ssh1: ruser from 1.2.3.4' '^ Failed \S+ for .*? from <HOST>( port \d*)?( ssh\d+)?(: ruser .*)?$'
|
||||
|
||||
1) [1] ^ Failed \S+ for .*? from <HOST>( port \d*)?( ssh\d+)?(: ruser .*)?$
|
||||
127.0.0.1 Sun Sep 29 17:15:02 2013
|
||||
|
||||
So the general case here is a log line that contains:
|
||||
|
||||
(fixed_data_1)<HOST>(fixed_data_2)(user_injectable_data)
|
||||
|
||||
Where the regex that matches fixed_data_1 is gready and matches the entire
|
||||
string, before moving backwards and user_injectable_data can match the entire
|
||||
string.
|
||||
|
||||
Another case:
|
||||
|
||||
ref: https://www.debuggex.com/r/CtAbeKMa2sDBEfA2/0
|
||||
|
||||
A webserver logs the following without URL escaping:
|
||||
|
||||
[error] 2865#0: *66647 user "xyz" was not found in "/file", client: 1.2.3.1, server: www.host.com, request: "GET ", client: 3.2.1.1, server: fake.com, request: "GET exploited HTTP/3.3", host: "injected.host", host: "www.myhost.com"
|
||||
|
||||
regex:
|
||||
|
||||
failregex = ^ \[error\] \d+#\d+: \*\d+ user "\S+":? (?:password mismatch|was not found in ".*"), client: <HOST>, server: \S+, request: "\S+ .+ HTTP/\d+\.\d+", host: "\S+"
|
||||
|
||||
The .* matches to the end of the string. Finds that it can't continue to match
|
||||
", client ... so it moves from the back and find that the user injected web URL:
|
||||
|
||||
", client: 3.2.1.1, server: fake.com, request: "GET exploited HTTP/3.3", host: "injected.host
|
||||
|
||||
In this case there is a fixed host: "www.myhost.com" at the end so the solution
|
||||
is to anchor the regex at the end with a $.
|
||||
|
||||
If this wasn't the case then first .* needed to be made so it didn't capture
|
||||
beyond <HOST>.
|
||||
|
||||
4. Application generates two identical log messages with different meanings
|
||||
|
||||
If the application generates the following two messages under different
|
||||
circumstances:
|
||||
|
||||
client <IP>: authentication failed
|
||||
client <USER>: authentication failed
|
||||
|
||||
|
||||
Then it's obvious that a regex of "^client <HOST>: authentication
|
||||
failed$" will still cause problems if the user can trigger the second
|
||||
log message with a <USER> of 123.1.1.1.
|
||||
|
||||
Here there's nothing to do except request/change the application so it logs
|
||||
messages differently.
|
||||
|
||||
|
25
MANIFEST
25
MANIFEST
|
@ -5,6 +5,7 @@ TODO
|
|||
THANKS
|
||||
COPYING
|
||||
DEVELOP
|
||||
FILTERS
|
||||
fail2ban-2to3
|
||||
fail2ban-testcases-all
|
||||
fail2ban-testcases-all-python3
|
||||
|
@ -25,6 +26,7 @@ fail2ban/client/__init__.py
|
|||
fail2ban/client/configurator.py
|
||||
fail2ban/client/csocket.py
|
||||
fail2ban/server/asyncserver.py
|
||||
fail2ban/server/database.py
|
||||
fail2ban/server/filter.py
|
||||
fail2ban/server/filterpyinotify.py
|
||||
fail2ban/server/filtergamin.py
|
||||
|
@ -62,6 +64,10 @@ fail2ban/tests/sockettestcase.py
|
|||
fail2ban/tests/utils.py
|
||||
fail2ban/tests/misctestcase.py
|
||||
fail2ban/tests/databasetestcase.py
|
||||
fail2ban/tests/config/jail.conf
|
||||
fail2ban/tests/config/fail2ban.conf
|
||||
fail2ban/tests/config/filter.d/simple.conf
|
||||
fail2ban/tests/config/action.d/brokenaction.conf
|
||||
fail2ban/tests/files/config/apache-auth/digest/.htaccess
|
||||
fail2ban/tests/files/config/apache-auth/digest/.htpasswd
|
||||
fail2ban/tests/files/config/apache-auth/digest_time/.htaccess
|
||||
|
@ -131,6 +137,7 @@ fail2ban/tests/files/logs/selinux-ssh
|
|||
fail2ban/tests/files/logs/sendmail-spam
|
||||
fail2ban/tests/files/logs/sieve
|
||||
fail2ban/tests/files/logs/squid
|
||||
fail2ban/tests/files/logs/stunnel
|
||||
fail2ban/tests/files/logs/suhosin
|
||||
fail2ban/tests/files/logs/sogo-auth
|
||||
fail2ban/tests/files/logs/solid-pop3d
|
||||
|
@ -156,6 +163,7 @@ setup.py
|
|||
setup.cfg
|
||||
kill-server
|
||||
config/jail.conf
|
||||
config/fail2ban.conf
|
||||
config/filter.d/common.conf
|
||||
config/filter.d/apache-auth.conf
|
||||
config/filter.d/apache-badbots.conf
|
||||
|
@ -170,17 +178,22 @@ config/filter.d/exim.conf
|
|||
config/filter.d/gssftpd.conf
|
||||
config/filter.d/suhosin.conf
|
||||
config/filter.d/named-refused.conf
|
||||
config/filter.d/openwebmail.conf
|
||||
config/filter.d/pam-generic.conf
|
||||
config/filter.d/php-url-fopen.conf
|
||||
config/filter.d/postfix-sasl.conf
|
||||
config/filter.d/pam-generic.conf
|
||||
config/filter.d/php-url-fopen.conf
|
||||
config/filter.d/postfix-sasl.conf
|
||||
config/filter.d/postfix.conf
|
||||
config/filter.d/proftpd.conf
|
||||
config/filter.d/pure-ftpd.conf
|
||||
config/filter.d/qmail.conf
|
||||
config/filter.d/pam-generic.conf
|
||||
config/filter.d/php-url-fopen.conf
|
||||
config/filter.d/postfix-sasl.conf
|
||||
config/filter.d/sieve.conf
|
||||
config/filter.d/solid-pop3d.conf
|
||||
config/filter.d/sshd.conf
|
||||
config/filter.d/sshd-ddos.conf
|
||||
config/filter.d/stunnel.conf
|
||||
config/filter.d/vsftpd.conf
|
||||
config/filter.d/webmin-auth.conf
|
||||
config/filter.d/wuftpd.conf
|
||||
|
@ -213,7 +226,8 @@ config/action.d/osx-ipfw.conf
|
|||
config/action.d/sendmail-common.conf
|
||||
config/action.d/bsd-ipfw.conf
|
||||
config/action.d/dummy.conf
|
||||
config/action.d/firewall-cmd-direct-new.conf
|
||||
config/action.d/firewallcmd-new.conf
|
||||
config/action.d/firewallcmd-ipset.conf
|
||||
config/action.d/iptables-ipset-proto6-allports.conf
|
||||
config/action.d/iptables-blocktype.conf
|
||||
config/action.d/iptables-ipset-proto4.conf
|
||||
|
@ -242,7 +256,8 @@ config/action.d/sendmail-whois-ipmatches.conf
|
|||
config/action.d/sendmail-whois.conf
|
||||
config/action.d/sendmail-whois-lines.conf
|
||||
config/action.d/shorewall.conf
|
||||
config/fail2ban.conf
|
||||
config/action.d/xarf-login-attack.conf
|
||||
config/action.d/ufw.conf
|
||||
doc/run-rootless.txt
|
||||
man/fail2ban-client.1
|
||||
man/fail2ban.1
|
||||
|
|
2
THANKS
2
THANKS
|
@ -35,9 +35,11 @@ ftoppi
|
|||
François Boulogne
|
||||
Frédéric
|
||||
Georgiy Mernov
|
||||
Guilhem Lettron
|
||||
Guillaume Delvit
|
||||
Hanno 'Rince' Wagner
|
||||
Iain Lea
|
||||
Ivo Truxa
|
||||
John Thoe
|
||||
Jacques Lav!gnotte
|
||||
Ioan Indreias
|
||||
|
|
|
@ -41,7 +41,7 @@ except ImportError:
|
|||
journal = None
|
||||
|
||||
from fail2ban.version import version
|
||||
from fail2ban.client.configparserinc import SafeConfigParserWithIncludes
|
||||
from fail2ban.client.filterreader import FilterReader
|
||||
from fail2ban.server.filter import Filter
|
||||
from fail2ban.server.failregex import RegexException
|
||||
|
||||
|
@ -206,8 +206,6 @@ class LineStats(object):
|
|||
|
||||
class Fail2banRegex(object):
|
||||
|
||||
CONFIG_DEFAULTS = {'configpath' : "/etc/fail2ban/"}
|
||||
|
||||
def __init__(self, opts):
|
||||
self._verbose = opts.verbose
|
||||
self._debuggex = opts.debuggex
|
||||
|
@ -257,46 +255,37 @@ class Fail2banRegex(object):
|
|||
assert(regextype in ('fail', 'ignore'))
|
||||
regex = regextype + 'regex'
|
||||
if os.path.isfile(value):
|
||||
reader = SafeConfigParserWithIncludes(defaults=self.CONFIG_DEFAULTS)
|
||||
try:
|
||||
reader.read(value)
|
||||
print "Use %11s file : %s" % (regex, value)
|
||||
# TODO: reuse functionality in client
|
||||
regex_values = [
|
||||
RegexStat(m)
|
||||
for m in reader.get("Definition", regex).split('\n')
|
||||
if m != ""]
|
||||
except NoSectionError:
|
||||
print "No [Definition] section in %s" % value
|
||||
return False
|
||||
except NoOptionError:
|
||||
print "No %s option in %s" % (regex, value)
|
||||
return False
|
||||
except MissingSectionHeaderError:
|
||||
print "No section headers in %s" % value
|
||||
return False
|
||||
reader = FilterReader(value, 'fail2ban-regex-jail', {})
|
||||
reader.setBaseDir(None)
|
||||
|
||||
if reader.readexplicit():
|
||||
reader.getOptions(None)
|
||||
readercommands = reader.convert()
|
||||
regex_values = [
|
||||
RegexStat(m[3])
|
||||
for m in filter(
|
||||
lambda x: x[0] == 'set' and x[2] == "add%sregex" % regextype,
|
||||
readercommands)]
|
||||
# Read out and set possible value of maxlines
|
||||
try:
|
||||
maxlines = reader.get("Init", "maxlines")
|
||||
except (NoSectionError, NoOptionError):
|
||||
# No [Init].maxlines found.
|
||||
pass
|
||||
else:
|
||||
for command in readercommands:
|
||||
if command[2] == "maxlines":
|
||||
maxlines = int(command[3])
|
||||
try:
|
||||
self.setMaxLines(maxlines)
|
||||
except ValueError:
|
||||
print "ERROR: Invalid value for maxlines (%(maxlines)r) " \
|
||||
"read from %(value)s" % locals()
|
||||
return False
|
||||
# Read out and set possible value for journalmatch
|
||||
try:
|
||||
journalmatch = reader.get("Init", "journalmatch")
|
||||
except (NoSectionError, NoOptionError):
|
||||
# No [Init].journalmatch found.
|
||||
pass
|
||||
else:
|
||||
elif command[2] == 'addjournalmatch':
|
||||
journalmatch = command[3]
|
||||
self.setJournalMatch(shlex.split(journalmatch))
|
||||
elif command[2] == 'datepattern':
|
||||
datepattern = command[3]
|
||||
self.setDatePattern(datepattern)
|
||||
else:
|
||||
print "ERROR: failed to read %s" % value
|
||||
return False
|
||||
else:
|
||||
print "Use %11s line : %s" % (regex, shortstr(value))
|
||||
regex_values = [RegexStat(value)]
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
# Fail2Ban action file for firewall-cmd/ipset
|
||||
#
|
||||
# This requires:
|
||||
# ipset (package: ipset)
|
||||
# firewall-cmd (package: firewalld)
|
||||
#
|
||||
# This is for ipset protocol 6 (and hopefully later) (ipset v6.14).
|
||||
# Use ipset -V to see the protocol and version.
|
||||
#
|
||||
# IPset was a feature introduced in the linux kernel 2.6.39 and 3.0.0 kernels.
|
||||
#
|
||||
# If you are running on an older kernel you make need to patch in external
|
||||
# modules.
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
before = iptables-blocktype.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
actionstart = ipset create fail2ban-<name> hash:ip timeout <bantime>
|
||||
firewall-cmd --direct --add-rule ipv4 filter <chain> 0 -p <protocol> -m multiport --dports <port> -m set --match-set fail2ban-<name> src -j <blocktype>
|
||||
|
||||
actionstop = firewall-cmd --direct --remove-rule ipv4 filter <chain> 0 -p <protocol> -m multiport --dports <port> -m set --match-set fail2ban-<name> src -j <blocktype>
|
||||
ipset flush fail2ban-<name>
|
||||
ipset destroy fail2ban-<name>
|
||||
|
||||
actioncheck = firewall-cmd --direct --get-chains ipv4 filter | grep -q '^fail2ban-<name>$'
|
||||
|
||||
actionban = ipset add fail2ban-<name> <ip> timeout <bantime> -exist
|
||||
|
||||
actionunban = ipset del fail2ban-<name> <ip> -exist
|
||||
|
||||
[Init]
|
||||
|
||||
# Default name of the chain
|
||||
#
|
||||
name = default
|
||||
|
||||
# Option: port
|
||||
# Notes.: specifies port to monitor
|
||||
# Values: [ NUM | STRING ]
|
||||
#
|
||||
port = ssh
|
||||
|
||||
# Option: protocol
|
||||
# Notes.: internally used by config reader for interpolations.
|
||||
# Values: [ tcp | udp | icmp | all ]
|
||||
#
|
||||
protocol = tcp
|
||||
|
||||
# Option: chain
|
||||
# Notes specifies the iptables chain to which the fail2ban rules should be
|
||||
# added
|
||||
# Values: [ STRING ]
|
||||
#
|
||||
chain = INPUT_direct
|
||||
|
||||
# Option: bantime
|
||||
# Notes: specifies the bantime in seconds (handled internally rather than by fail2ban)
|
||||
# Values: [ NUM ] Default: 600
|
||||
|
||||
bantime = 600
|
||||
|
||||
|
||||
# DEV NOTES:
|
||||
#
|
||||
# Author: Edgar Hoch and Daniel Black
|
||||
# firewallcmd-new / iptables-ipset-proto6 combined for maximium goodness
|
|
@ -8,19 +8,19 @@ before = iptables-blocktype.conf
|
|||
|
||||
[Definition]
|
||||
|
||||
actionstart = firewall-cmd --direct --add-chain ipv4 filter fail2ban-<name>
|
||||
firewall-cmd --direct --add-rule ipv4 filter fail2ban-<name> 1000 -j RETURN
|
||||
firewall-cmd --direct --add-rule ipv4 filter <chain> 0 -m state --state NEW -p <protocol> --dport <port> -j fail2ban-<name>
|
||||
actionstart = firewall-cmd --direct --add-chain ipv4 filter f2b-<name>
|
||||
firewall-cmd --direct --add-rule ipv4 filter f2b-<name> 1000 -j RETURN
|
||||
firewall-cmd --direct --add-rule ipv4 filter <chain> 0 -m state --state NEW -p <protocol> --dport <port> -j f2b-<name>
|
||||
|
||||
actionstop = firewall-cmd --direct --remove-rule ipv4 filter <chain> 0 -m state --state NEW -p <protocol> --dport <port> -j fail2ban-<name>
|
||||
firewall-cmd --direct --remove-rules ipv4 filter fail2ban-<name>
|
||||
firewall-cmd --direct --remove-chain ipv4 filter fail2ban-<name>
|
||||
actionstop = firewall-cmd --direct --remove-rule ipv4 filter <chain> 0 -m state --state NEW -p <protocol> --dport <port> -j f2b-<name>
|
||||
firewall-cmd --direct --remove-rules ipv4 filter f2b-<name>
|
||||
firewall-cmd --direct --remove-chain ipv4 filter f2b-<name>
|
||||
|
||||
actioncheck = firewall-cmd --direct --get-chains ipv4 filter | grep -q '^fail2ban-<name>$'
|
||||
actioncheck = firewall-cmd --direct --get-chains ipv4 filter | grep -q 'f2b-<name>$'
|
||||
|
||||
actionban = firewall-cmd --direct --add-rule ipv4 filter fail2ban-<name> 0 -s <ip> -j <blocktype>
|
||||
actionban = firewall-cmd --direct --add-rule ipv4 filter f2b-<name> 0 -s <ip> -j <blocktype>
|
||||
|
||||
actionunban = firewall-cmd --direct --remove-rule ipv4 filter fail2ban-<name> 0 -s <ip> -j <blocktype>
|
||||
actionunban = firewall-cmd --direct --remove-rule ipv4 filter f2b-<name> 0 -s <ip> -j <blocktype>
|
||||
|
||||
[Init]
|
||||
|
||||
|
|
|
@ -17,23 +17,23 @@ before = iptables-blocktype.conf
|
|||
# Notes.: command executed once at the start of Fail2Ban.
|
||||
# Values: CMD
|
||||
#
|
||||
actionstart = iptables -N fail2ban-<name>
|
||||
iptables -A fail2ban-<name> -j RETURN
|
||||
iptables -I <chain> -p <protocol> -j fail2ban-<name>
|
||||
actionstart = iptables -N f2b-<name>
|
||||
iptables -A f2b-<name> -j RETURN
|
||||
iptables -I <chain> -p <protocol> -j f2b-<name>
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
#
|
||||
actionstop = iptables -D <chain> -p <protocol> -j fail2ban-<name>
|
||||
iptables -F fail2ban-<name>
|
||||
iptables -X fail2ban-<name>
|
||||
actionstop = iptables -D <chain> -p <protocol> -j f2b-<name>
|
||||
iptables -F f2b-<name>
|
||||
iptables -X f2b-<name>
|
||||
|
||||
# Option: actioncheck
|
||||
# Notes.: command executed once before each actionban command
|
||||
# Values: CMD
|
||||
#
|
||||
actioncheck = iptables -n -L <chain> | grep -q 'fail2ban-<name>[ \t]'
|
||||
actioncheck = iptables -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
|
@ -41,7 +41,7 @@ actioncheck = iptables -n -L <chain> | grep -q 'fail2ban-<name>[ \t]'
|
|||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = iptables -I fail2ban-<name> 1 -s <ip> -j <blocktype>
|
||||
actionban = iptables -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
|
@ -49,7 +49,7 @@ actionban = iptables -I fail2ban-<name> 1 -s <ip> -j <blocktype>
|
|||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = iptables -D fail2ban-<name> -s <ip> -j <blocktype>
|
||||
actionunban = iptables -D f2b-<name> -s <ip> -j <blocktype>
|
||||
|
||||
[Init]
|
||||
|
||||
|
|
|
@ -27,16 +27,16 @@ before = iptables-blocktype.conf
|
|||
# Notes.: command executed once at the start of Fail2Ban.
|
||||
# Values: CMD
|
||||
#
|
||||
actionstart = ipset --create fail2ban-<name> iphash
|
||||
iptables -I INPUT -p <protocol> -m multiport --dports <port> -m set --match-set fail2ban-<name> src -j <blocktype>
|
||||
actionstart = ipset --create f2b-<name> iphash
|
||||
iptables -I INPUT -p <protocol> -m multiport --dports <port> -m set --match-set f2b-<name> src -j <blocktype>
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
#
|
||||
actionstop = iptables -D INPUT -p <protocol> -m multiport --dports <port> -m set --match-set fail2ban-<name> src -j <blocktype>
|
||||
ipset --flush fail2ban-<name>
|
||||
ipset --destroy fail2ban-<name>
|
||||
actionstop = iptables -D INPUT -p <protocol> -m multiport --dports <port> -m set --match-set f2b-<name> src -j <blocktype>
|
||||
ipset --flush f2b-<name>
|
||||
ipset --destroy f2b-<name>
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
|
@ -44,7 +44,7 @@ actionstop = iptables -D INPUT -p <protocol> -m multiport --dports <port> -m set
|
|||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = ipset --test fail2ban-<name> <ip> || ipset --add fail2ban-<name> <ip>
|
||||
actionban = ipset --test f2b-<name> <ip> || ipset --add f2b-<name> <ip>
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
|
@ -52,7 +52,7 @@ actionban = ipset --test fail2ban-<name> <ip> || ipset --add fail2ban-<name> <i
|
|||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = ipset --test fail2ban-<name> <ip> && ipset --del fail2ban-<name> <ip>
|
||||
actionunban = ipset --test f2b-<name> <ip> && ipset --del f2b-<name> <ip>
|
||||
|
||||
[Init]
|
||||
|
||||
|
|
|
@ -24,16 +24,16 @@ before = iptables-blocktype.conf
|
|||
# Notes.: command executed once at the start of Fail2Ban.
|
||||
# Values: CMD
|
||||
#
|
||||
actionstart = ipset create fail2ban-<name> hash:ip timeout <bantime>
|
||||
iptables -I INPUT -m set --match-set fail2ban-<name> src -j <blocktype>
|
||||
actionstart = ipset create f2b-<name> hash:ip timeout <bantime>
|
||||
iptables -I INPUT -m set --match-set f2b-<name> src -j <blocktype>
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
#
|
||||
actionstop = iptables -D INPUT -m set --match-set fail2ban-<name> src -j <blocktype>
|
||||
ipset flush fail2ban-<name>
|
||||
ipset destroy fail2ban-<name>
|
||||
actionstop = iptables -D INPUT -m set --match-set f2b-<name> src -j <blocktype>
|
||||
ipset flush f2b-<name>
|
||||
ipset destroy f2b-<name>
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
|
@ -41,7 +41,7 @@ actionstop = iptables -D INPUT -m set --match-set fail2ban-<name> src -j <blockt
|
|||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = ipset add fail2ban-<name> <ip> timeout <bantime> -exist
|
||||
actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
|
@ -49,7 +49,7 @@ actionban = ipset add fail2ban-<name> <ip> timeout <bantime> -exist
|
|||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = ipset del fail2ban-<name> <ip> -exist
|
||||
actionunban = ipset del f2b-<name> <ip> -exist
|
||||
|
||||
[Init]
|
||||
|
||||
|
|
|
@ -24,16 +24,16 @@ before = iptables-blocktype.conf
|
|||
# Notes.: command executed once at the start of Fail2Ban.
|
||||
# Values: CMD
|
||||
#
|
||||
actionstart = ipset create fail2ban-<name> hash:ip timeout <bantime>
|
||||
iptables -I INPUT -p <protocol> -m multiport --dports <port> -m set --match-set fail2ban-<name> src -j <blocktype>
|
||||
actionstart = ipset create f2b-<name> hash:ip timeout <bantime>
|
||||
iptables -I INPUT -p <protocol> -m multiport --dports <port> -m set --match-set f2b-<name> src -j <blocktype>
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
#
|
||||
actionstop = iptables -D INPUT -p <protocol> -m multiport --dports <port> -m set --match-set fail2ban-<name> src -j <blocktype>
|
||||
ipset flush fail2ban-<name>
|
||||
ipset destroy fail2ban-<name>
|
||||
actionstop = iptables -D INPUT -p <protocol> -m multiport --dports <port> -m set --match-set f2b-<name> src -j <blocktype>
|
||||
ipset flush f2b-<name>
|
||||
ipset destroy f2b-<name>
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
|
@ -41,7 +41,7 @@ actionstop = iptables -D INPUT -p <protocol> -m multiport --dports <port> -m set
|
|||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = ipset add fail2ban-<name> <ip> timeout <bantime> -exist
|
||||
actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
|
@ -49,7 +49,7 @@ actionban = ipset add fail2ban-<name> <ip> timeout <bantime> -exist
|
|||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = ipset del fail2ban-<name> <ip> -exist
|
||||
actionunban = ipset del f2b-<name> <ip> -exist
|
||||
|
||||
[Init]
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
# Author: Guido Bozzetto
|
||||
# Modified: Cyril Jaquier
|
||||
#
|
||||
# make "fail2ban-<name>" chain to match drop IP
|
||||
# make "fail2ban-<name>-log" chain to log and drop
|
||||
# insert a jump to fail2ban-<name> from -I <chain> if proto/port match
|
||||
# make "f2b-<name>" chain to match drop IP
|
||||
# make "f2b-<name>-log" chain to log and drop
|
||||
# insert a jump to f2b-<name> from -I <chain> if proto/port match
|
||||
#
|
||||
#
|
||||
|
||||
|
@ -19,28 +19,28 @@ before = iptables-blocktype.conf
|
|||
# Notes.: command executed once at the start of Fail2Ban.
|
||||
# Values: CMD
|
||||
#
|
||||
actionstart = iptables -N fail2ban-<name>
|
||||
iptables -A fail2ban-<name> -j RETURN
|
||||
iptables -I <chain> 1 -p <protocol> -m multiport --dports <port> -j fail2ban-<name>
|
||||
iptables -N fail2ban-<name>-log
|
||||
iptables -I fail2ban-<name>-log -j LOG --log-prefix "$(expr fail2ban-<name> : '\(.\{1,23\}\)'):DROP " --log-level warning -m limit --limit 6/m --limit-burst 2
|
||||
iptables -A fail2ban-<name>-log -j <blocktype>
|
||||
actionstart = iptables -N f2b-<name>
|
||||
iptables -A f2b-<name> -j RETURN
|
||||
iptables -I <chain> 1 -p <protocol> -m multiport --dports <port> -j f2b-<name>
|
||||
iptables -N f2b-<name>-log
|
||||
iptables -I f2b-<name>-log -j LOG --log-prefix "$(expr f2b-<name> : '\(.\{1,23\}\)'):DROP " --log-level warning -m limit --limit 6/m --limit-burst 2
|
||||
iptables -A f2b-<name>-log -j <blocktype>
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
#
|
||||
actionstop = iptables -D <chain> -p <protocol> -m multiport --dports <port> -j fail2ban-<name>
|
||||
iptables -F fail2ban-<name>
|
||||
iptables -F fail2ban-<name>-log
|
||||
iptables -X fail2ban-<name>
|
||||
iptables -X fail2ban-<name>-log
|
||||
actionstop = iptables -D <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
|
||||
iptables -F f2b-<name>
|
||||
iptables -F f2b-<name>-log
|
||||
iptables -X f2b-<name>
|
||||
iptables -X f2b-<name>-log
|
||||
|
||||
# Option: actioncheck
|
||||
# Notes.: command executed once before each actionban command
|
||||
# Values: CMD
|
||||
#
|
||||
actioncheck = iptables -n -L fail2ban-<name>-log >/dev/null
|
||||
actioncheck = iptables -n -L f2b-<name>-log >/dev/null
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
|
@ -48,7 +48,7 @@ actioncheck = iptables -n -L fail2ban-<name>-log >/dev/null
|
|||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = iptables -I fail2ban-<name> 1 -s <ip> -j fail2ban-<name>-log
|
||||
actionban = iptables -I f2b-<name> 1 -s <ip> -j f2b-<name>-log
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
|
@ -56,7 +56,7 @@ actionban = iptables -I fail2ban-<name> 1 -s <ip> -j fail2ban-<name>-log
|
|||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = iptables -D fail2ban-<name> -s <ip> -j fail2ban-<name>-log
|
||||
actionunban = iptables -D f2b-<name> -s <ip> -j f2b-<name>-log
|
||||
|
||||
[Init]
|
||||
|
||||
|
|
|
@ -14,23 +14,23 @@ before = iptables-blocktype.conf
|
|||
# Notes.: command executed once at the start of Fail2Ban.
|
||||
# Values: CMD
|
||||
#
|
||||
actionstart = iptables -N fail2ban-<name>
|
||||
iptables -A fail2ban-<name> -j RETURN
|
||||
iptables -I <chain> -p <protocol> -m multiport --dports <port> -j fail2ban-<name>
|
||||
actionstart = iptables -N f2b-<name>
|
||||
iptables -A f2b-<name> -j RETURN
|
||||
iptables -I <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
#
|
||||
actionstop = iptables -D <chain> -p <protocol> -m multiport --dports <port> -j fail2ban-<name>
|
||||
iptables -F fail2ban-<name>
|
||||
iptables -X fail2ban-<name>
|
||||
actionstop = iptables -D <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
|
||||
iptables -F f2b-<name>
|
||||
iptables -X f2b-<name>
|
||||
|
||||
# Option: actioncheck
|
||||
# Notes.: command executed once before each actionban command
|
||||
# Values: CMD
|
||||
#
|
||||
actioncheck = iptables -n -L <chain> | grep -q 'fail2ban-<name>[ \t]'
|
||||
actioncheck = iptables -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
|
@ -38,7 +38,7 @@ actioncheck = iptables -n -L <chain> | grep -q 'fail2ban-<name>[ \t]'
|
|||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = iptables -I fail2ban-<name> 1 -s <ip> -j <blocktype>
|
||||
actionban = iptables -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
|
@ -46,7 +46,7 @@ actionban = iptables -I fail2ban-<name> 1 -s <ip> -j <blocktype>
|
|||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = iptables -D fail2ban-<name> -s <ip> -j <blocktype>
|
||||
actionunban = iptables -D f2b-<name> -s <ip> -j <blocktype>
|
||||
|
||||
[Init]
|
||||
|
||||
|
|
|
@ -17,23 +17,23 @@ before = iptables-blocktype.conf
|
|||
# Notes.: command executed once at the start of Fail2Ban.
|
||||
# Values: CMD
|
||||
#
|
||||
actionstart = iptables -N fail2ban-<name>
|
||||
iptables -A fail2ban-<name> -j RETURN
|
||||
iptables -I <chain> -m state --state NEW -p <protocol> --dport <port> -j fail2ban-<name>
|
||||
actionstart = iptables -N f2b-<name>
|
||||
iptables -A f2b-<name> -j RETURN
|
||||
iptables -I <chain> -m state --state NEW -p <protocol> --dport <port> -j f2b-<name>
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
#
|
||||
actionstop = iptables -D <chain> -m state --state NEW -p <protocol> --dport <port> -j fail2ban-<name>
|
||||
iptables -F fail2ban-<name>
|
||||
iptables -X fail2ban-<name>
|
||||
actionstop = iptables -D <chain> -m state --state NEW -p <protocol> --dport <port> -j f2b-<name>
|
||||
iptables -F f2b-<name>
|
||||
iptables -X f2b-<name>
|
||||
|
||||
# Option: actioncheck
|
||||
# Notes.: command executed once before each actionban command
|
||||
# Values: CMD
|
||||
#
|
||||
actioncheck = iptables -n -L <chain> | grep -q 'fail2ban-<name>[ \t]'
|
||||
actioncheck = iptables -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
|
@ -41,7 +41,7 @@ actioncheck = iptables -n -L <chain> | grep -q 'fail2ban-<name>[ \t]'
|
|||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = iptables -I fail2ban-<name> 1 -s <ip> -j <blocktype>
|
||||
actionban = iptables -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
|
@ -49,7 +49,7 @@ actionban = iptables -I fail2ban-<name> 1 -s <ip> -j <blocktype>
|
|||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = iptables -D fail2ban-<name> -s <ip> -j <blocktype>
|
||||
actionunban = iptables -D f2b-<name> -s <ip> -j <blocktype>
|
||||
|
||||
[Init]
|
||||
|
||||
|
|
|
@ -23,29 +23,29 @@ before = iptables-blocktype.conf
|
|||
# iptables-persistent package).
|
||||
#
|
||||
# Explanation of the rule below:
|
||||
# Check if any packets coming from an IP on the fail2ban-<name>
|
||||
# Check if any packets coming from an IP on the f2b-<name>
|
||||
# list have been seen in the last 3600 seconds. If yes, update the
|
||||
# timestamp for this IP and drop the packet. If not, let the packet
|
||||
# through.
|
||||
#
|
||||
# Fail2ban inserts blacklisted hosts into the fail2ban-<name> list
|
||||
# Fail2ban inserts blacklisted hosts into the f2b-<name> list
|
||||
# and removes them from the list after some time, according to its
|
||||
# own rules. The 3600 second timeout is independent and acts as a
|
||||
# safeguard in case the fail2ban process dies unexpectedly. The
|
||||
# shorter of the two timeouts actually matters.
|
||||
actionstart = iptables -I INPUT -m recent --update --seconds 3600 --name fail2ban-<name> -j <blocktype>
|
||||
actionstart = iptables -I INPUT -m recent --update --seconds 3600 --name f2b-<name> -j <blocktype>
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
#
|
||||
actionstop = echo / > /proc/net/xt_recent/fail2ban-<name>
|
||||
actionstop = echo / > /proc/net/xt_recent/f2b-<name>
|
||||
|
||||
# Option: actioncheck
|
||||
# Notes.: command executed once before each actionban command
|
||||
# Values: CMD
|
||||
#
|
||||
actioncheck = test -e /proc/net/xt_recent/fail2ban-<name>
|
||||
actioncheck = test -e /proc/net/xt_recent/f2b-<name>
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
|
@ -53,7 +53,7 @@ actioncheck = test -e /proc/net/xt_recent/fail2ban-<name>
|
|||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = echo +<ip> > /proc/net/xt_recent/fail2ban-<name>
|
||||
actionban = echo +<ip> > /proc/net/xt_recent/f2b-<name>
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
|
@ -61,7 +61,7 @@ actionban = echo +<ip> > /proc/net/xt_recent/fail2ban-<name>
|
|||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = echo -<ip> > /proc/net/xt_recent/fail2ban-<name>
|
||||
actionunban = echo -<ip> > /proc/net/xt_recent/f2b-<name>
|
||||
|
||||
[Init]
|
||||
|
||||
|
|
|
@ -14,23 +14,23 @@ before = iptables-blocktype.conf
|
|||
# Notes.: command executed once at the start of Fail2Ban.
|
||||
# Values: CMD
|
||||
#
|
||||
actionstart = iptables -N fail2ban-<name>
|
||||
iptables -A fail2ban-<name> -j RETURN
|
||||
iptables -I <chain> -p <protocol> --dport <port> -j fail2ban-<name>
|
||||
actionstart = iptables -N f2b-<name>
|
||||
iptables -A f2b-<name> -j RETURN
|
||||
iptables -I <chain> -p <protocol> --dport <port> -j f2b-<name>
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
#
|
||||
actionstop = iptables -D <chain> -p <protocol> --dport <port> -j fail2ban-<name>
|
||||
iptables -F fail2ban-<name>
|
||||
iptables -X fail2ban-<name>
|
||||
actionstop = iptables -D <chain> -p <protocol> --dport <port> -j f2b-<name>
|
||||
iptables -F f2b-<name>
|
||||
iptables -X f2b-<name>
|
||||
|
||||
# Option: actioncheck
|
||||
# Notes.: command executed once before each actionban command
|
||||
# Values: CMD
|
||||
#
|
||||
actioncheck = iptables -n -L <chain> | grep -q 'fail2ban-<name>[ \t]'
|
||||
actioncheck = iptables -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
|
@ -38,7 +38,7 @@ actioncheck = iptables -n -L <chain> | grep -q 'fail2ban-<name>[ \t]'
|
|||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = iptables -I fail2ban-<name> 1 -s <ip> -j <blocktype>
|
||||
actionban = iptables -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
|
@ -46,7 +46,7 @@ actionban = iptables -I fail2ban-<name> 1 -s <ip> -j <blocktype>
|
|||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = iptables -D fail2ban-<name> -s <ip> -j <blocktype>
|
||||
actionunban = iptables -D f2b-<name> -s <ip> -j <blocktype>
|
||||
|
||||
[Init]
|
||||
|
||||
|
|
|
@ -0,0 +1,225 @@
|
|||
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
|
||||
# vi: set ft=python sts=4 ts=4 sw=4 noet :
|
||||
|
||||
# This file is part of Fail2Ban.
|
||||
#
|
||||
# Fail2Ban is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Fail2Ban is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Fail2Ban; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
import sys
|
||||
import socket
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.utils import formatdate, formataddr
|
||||
|
||||
from fail2ban.server.actions import ActionBase, CallingMap
|
||||
|
||||
messages = {}
|
||||
messages['start'] = \
|
||||
"""Hi,
|
||||
|
||||
The jail %(jailname)s has been started successfully.
|
||||
|
||||
Regards,
|
||||
Fail2Ban"""
|
||||
|
||||
messages['stop'] = \
|
||||
"""Hi,
|
||||
|
||||
The jail %(jailname)s has been stopped.
|
||||
|
||||
Regards,
|
||||
Fail2Ban"""
|
||||
|
||||
messages['ban'] = {}
|
||||
messages['ban']['head'] = \
|
||||
"""Hi,
|
||||
|
||||
The IP %(ip)s has just been banned for %(bantime)s seconds
|
||||
by Fail2Ban after %(failures)i attempts against %(jailname)s.
|
||||
"""
|
||||
messages['ban']['tail'] = \
|
||||
"""
|
||||
Regards,
|
||||
Fail2Ban"""
|
||||
messages['ban']['matches'] = \
|
||||
"""
|
||||
Matches for this ban:
|
||||
%(matches)s
|
||||
"""
|
||||
messages['ban']['ipmatches'] = \
|
||||
"""
|
||||
Matches for %(ip)s:
|
||||
%(ipmatches)s
|
||||
"""
|
||||
messages['ban']['ipjailmatches'] = \
|
||||
"""
|
||||
Matches for %(ip)s for jail %(jailname)s:
|
||||
%(ipjailmatches)s
|
||||
"""
|
||||
|
||||
class SMTPAction(ActionBase):
|
||||
"""Fail2Ban action which sends emails to inform on jail starting,
|
||||
stopping and bans.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, jail, name, host="localhost", user=None, password=None,
|
||||
sendername="Fail2Ban", sender="fail2ban", dest="root", matches=None):
|
||||
"""Initialise action.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
jail : Jail
|
||||
The jail which the action belongs to.
|
||||
name : str
|
||||
Named assigned to the action.
|
||||
host : str, optional
|
||||
SMTP host, of host:port format. Default host "localhost" and
|
||||
port "25"
|
||||
user : str, optional
|
||||
Username used for authentication with SMTP server.
|
||||
password : str, optional
|
||||
Password used for authentication with SMTP server.
|
||||
sendername : str, optional
|
||||
Name to use for from address in email. Default "Fail2Ban".
|
||||
sender : str, optional
|
||||
Email address to use for from address in email.
|
||||
Default "fail2ban".
|
||||
dest : str, optional
|
||||
Email addresses of intended recipient(s) in comma delimited
|
||||
format. Default "root".
|
||||
matches : str, optional
|
||||
Type of matches to be included from ban in email. Can be one
|
||||
of "matches", "ipmatches" or "ipjailmatches". Default None
|
||||
(see man jail.conf.5).
|
||||
"""
|
||||
|
||||
super(SMTPAction, self).__init__(jail, name)
|
||||
|
||||
self.host = host
|
||||
#TODO: self.ssl = ssl
|
||||
|
||||
self.user = user
|
||||
self.password =password
|
||||
|
||||
self.fromname = sendername
|
||||
self.fromaddr = sender
|
||||
self.toaddr = dest
|
||||
|
||||
self.matches = matches
|
||||
|
||||
self.message_values = CallingMap(
|
||||
jailname = self._jail.getName(), # Doesn't change
|
||||
hostname = socket.gethostname,
|
||||
bantime = self._jail.actions.getBanTime,
|
||||
)
|
||||
|
||||
def _sendMessage(self, subject, text):
|
||||
"""Sends message based on arguments and instance's properties.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
subject : str
|
||||
Subject of the email.
|
||||
text : str
|
||||
Body of the email.
|
||||
|
||||
Raises
|
||||
------
|
||||
SMTPConnectionError
|
||||
Error on connecting to host.
|
||||
SMTPAuthenticationError
|
||||
Error authenticating with SMTP server.
|
||||
SMTPException
|
||||
See Python `smtplib` for full list of other possible
|
||||
exceptions.
|
||||
"""
|
||||
msg = MIMEText(text)
|
||||
msg['Subject'] = subject
|
||||
msg['From'] = formataddr((self.fromname, self.fromaddr))
|
||||
msg['To'] = self.toaddr
|
||||
msg['Date'] = formatdate()
|
||||
|
||||
smtp = smtplib.SMTP()
|
||||
try:
|
||||
self._logSys.debug("Connected to SMTP '%s', response: %i: %s",
|
||||
self.host, *smtp.connect(self.host))
|
||||
if self.user and self.password:
|
||||
smtp.login(self.user, self.password)
|
||||
failed_recipients = smtp.sendmail(
|
||||
self.fromaddr, self.toaddr, msg.as_string())
|
||||
except smtplib.SMTPConnectError:
|
||||
self._logSys.error("Error connecting to host '%s'", self.host)
|
||||
raise
|
||||
except smtplib.SMTPAuthenticationError:
|
||||
self._logSys.error(
|
||||
"Failed to authenticate with host '%s' user '%s'",
|
||||
self.host, self.user)
|
||||
raise
|
||||
except smtplib.SMTPException:
|
||||
self._logSys.error(
|
||||
"Error sending mail to host '%s' from '%s' to '%s'",
|
||||
self.host, self.fromaddr, self.toaddr)
|
||||
raise
|
||||
else:
|
||||
if failed_recipients:
|
||||
self._logSys.warning(
|
||||
"Email to '%s' failed to following recipients: %r",
|
||||
self.toaddr, failed_recipients)
|
||||
self._logSys.debug("Email '%s' successfully sent", subject)
|
||||
finally:
|
||||
try:
|
||||
self._logSys.debug("Disconnected from '%s', response %i: %s",
|
||||
self.host, *smtp.quit())
|
||||
except smtplib.SMTPServerDisconnected:
|
||||
pass # Not connected
|
||||
|
||||
def start(self):
|
||||
"""Sends email to recipients informing that the jail has started.
|
||||
"""
|
||||
self._sendMessage(
|
||||
"[Fail2Ban] %(jailname)s: started on %(hostname)s" %
|
||||
self.message_values,
|
||||
messages['start'] % self.message_values)
|
||||
|
||||
def stop(self):
|
||||
"""Sends email to recipients informing that the jail has stopped.
|
||||
"""
|
||||
self._sendMessage(
|
||||
"[Fail2Ban] %(jailname)s: stopped on %(hostname)s" %
|
||||
self.message_values,
|
||||
messages['stop'] % self.message_values)
|
||||
|
||||
def ban(self, aInfo):
|
||||
"""Sends email to recipients informing that ban has occurred.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
aInfo : dict
|
||||
Dictionary which includes information in relation to
|
||||
the ban.
|
||||
"""
|
||||
aInfo.update(self.message_values)
|
||||
message = "".join([
|
||||
messages['ban']['head'],
|
||||
messages['ban'].get(self.matches, ""),
|
||||
messages['ban']['tail']
|
||||
])
|
||||
self._sendMessage(
|
||||
"[Fail2Ban] %(jailname)s: banned %(ip)s from %(hostname)s" %
|
||||
aInfo,
|
||||
message % aInfo)
|
||||
|
||||
Action = SMTPAction
|
|
@ -0,0 +1,40 @@
|
|||
# Fail2Ban action configuration file for ufw
|
||||
#
|
||||
# You are required to run "ufw enable" before this will have an effect.
|
||||
#
|
||||
# The insert position should be approprate to block the required traffic.
|
||||
# A number after an allow rule to the application won't be much use.
|
||||
|
||||
[Definition]
|
||||
|
||||
actionstart =
|
||||
|
||||
actionstop =
|
||||
|
||||
actioncheck =
|
||||
|
||||
actionban = [ -n "<application>" ] && app="app <application>" ; ufw insert <insertpos> <blocktype> from <ip> to <destination> $app
|
||||
|
||||
actionunban = [ -n "<application>" ] && app="app <application>" ; ufw delete <blocktype> from <ip> to <destination> $app
|
||||
|
||||
[Init]
|
||||
# Option: insertpos
|
||||
# Notes.: The postition number in the firewall list to insert the block rule
|
||||
insertpos = 1
|
||||
|
||||
# Option: blocktype
|
||||
# Notes.: reject or deny
|
||||
blocktype = reject
|
||||
|
||||
# Option: destination
|
||||
# Notes.: The destination address to block in the ufw rule
|
||||
destination = any
|
||||
|
||||
# Option: application
|
||||
# Notes.: application from sudo ufw app list
|
||||
application =
|
||||
|
||||
# DEV NOTES:
|
||||
#
|
||||
# Author: Guilhem Lettron
|
||||
# Enhancements: Daniel Black
|
|
@ -11,7 +11,12 @@
|
|||
#
|
||||
# honeypot: :blackhole:
|
||||
#
|
||||
# The use the jail.local filter = exim-spam[honeypot=honeypot@yourdomain.com]
|
||||
# For the SA: Action: silently tossed message... to be logged exim's SAdevnull option needs to be used.
|
||||
#
|
||||
# To this filter use the jail.local should contain in the right jail:
|
||||
#
|
||||
# filter = exim-spam[honeypot=honeypot@yourdomain.com]
|
||||
#
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
|
@ -25,6 +30,7 @@ failregex = ^%(pid)s \S+ F=(<>|\S+@\S+) %(host_info)srejected by local_scan\(\)
|
|||
^%(pid)s %(host_info)sF=(<>|[^@]+@\S+) rejected RCPT [^@]+@\S+: .*dnsbl.*\s*$
|
||||
^%(pid)s \S+ %(host_info)sF=(<>|[^@]+@\S+) rejected after DATA: This message contains a virus \(\S+\)\.\s*$
|
||||
^%(pid)s \S+ SA: Action: flagged as Spam but accepted: score=\d+\.\d+ required=\d+\.\d+ \(scanned in \d+/\d+ secs \| Message-Id: \S+\)\. From \S+ \(host=\S+ \[<HOST>\]\) for <honeypot>$
|
||||
^%(pid)s \S+ SA: Action: silently tossed message: score=\d+\.\d+ required=\d+\.\d+ trigger=\d+\.\d+ \(scanned in \d+/\d+ secs \| Message-Id: \S+\)\. From \S+ \(host=(\S+ )?\[<HOST>\]\) for \S+$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# fail2ban filter configuration for horde
|
||||
|
||||
|
||||
[Definition]
|
||||
|
||||
|
||||
failregex = ^ HORDE \[error\] \[(horde|imp)\] FAILED LOGIN for \S+ \[<HOST>\](\(forwarded for \[\S+\]\))? to (Horde|{[^}]+}) \[(pid \d+ )?on line \d+ of \S+\]$
|
||||
|
||||
|
||||
ignoreregex =
|
||||
|
||||
# DEV NOTES:
|
||||
# https://github.com/horde/horde/blob/master/imp/lib/Auth.php#L132
|
||||
# https://github.com/horde/horde/blob/master/horde/login.php
|
||||
#
|
||||
# Author: Daniel Black
|
|
@ -0,0 +1,15 @@
|
|||
# Fail2Ban filter for Openwebmail
|
||||
# banning hosts with authentication errors in /var/log/openwebmail.log
|
||||
# OpenWebMail http://openwebmail.org
|
||||
#
|
||||
|
||||
[Definition]
|
||||
|
||||
failregex = ^ - \[\d+\] \(<HOST>\) (?P<USER>\S+) - login error - (no such user - loginname=(?P=USER)|auth_unix.pl, ret -4, Password incorrect)$
|
||||
^ - \[\d+\] \(<HOST>\) (?P<USER>\S+) - userinfo error - auth_unix.pl, ret -4, User (?P=USER) doesn't exist$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
# DEV Notes:
|
||||
#
|
||||
# Author: Ivo Truxa (c) 2013 truXoft.com
|
|
@ -0,0 +1,11 @@
|
|||
# Fail2ban filter for stunnel
|
||||
|
||||
[Definition]
|
||||
|
||||
failregex = ^ LOG\d\[\d+:\d+\]:\ SSL_accept from <HOST>:\d+ : (?P<CODE>[\dA-F]+): error:(?P=CODE):SSL routines:SSL3_GET_CLIENT_CERTIFICATE:peer did not return a certificate$
|
||||
|
||||
# DEV NOTES:
|
||||
#
|
||||
# Author: Daniel Black
|
||||
#
|
||||
# Based off: http://www.fail2ban.org/wiki/index.php/Fail2ban:Community_Portal#stunnel4
|
|
@ -390,6 +390,11 @@ port = http,https
|
|||
logpath = /var/log/roundcube/userlogins
|
||||
|
||||
|
||||
[openwebmail]
|
||||
|
||||
port = http,https
|
||||
logpath = /var/log/openwebmail.log`
|
||||
|
||||
[sogo-auth]
|
||||
# Monitor SOGo groupware server
|
||||
# without proxy this would be:
|
||||
|
|
|
@ -25,7 +25,8 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
|||
__license__ = "GPL"
|
||||
|
||||
import logging, os
|
||||
from configreader import ConfigReader, DefinitionInitConfigReader
|
||||
|
||||
from .configreader import ConfigReader, DefinitionInitConfigReader
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
@ -58,22 +59,20 @@ class ActionReader(DefinitionInitConfigReader):
|
|||
head = ["set", self._jailName]
|
||||
stream = list()
|
||||
stream.append(head + ["addaction", self._name])
|
||||
head.extend(["action", self._name])
|
||||
for opt in self._opts:
|
||||
if opt == "actionstart":
|
||||
stream.append(head + ["actionstart", self._name, self._opts[opt]])
|
||||
stream.append(head + ["actionstart", self._opts[opt]])
|
||||
elif opt == "actionstop":
|
||||
stream.append(head + ["actionstop", self._name, self._opts[opt]])
|
||||
stream.append(head + ["actionstop", self._opts[opt]])
|
||||
elif opt == "actioncheck":
|
||||
stream.append(head + ["actioncheck", self._name, self._opts[opt]])
|
||||
stream.append(head + ["actioncheck", self._opts[opt]])
|
||||
elif opt == "actionban":
|
||||
stream.append(head + ["actionban", self._name, self._opts[opt]])
|
||||
stream.append(head + ["actionban", self._opts[opt]])
|
||||
elif opt == "actionunban":
|
||||
stream.append(head + ["actionunban", self._name, self._opts[opt]])
|
||||
stream.append(head + ["actionunban", self._opts[opt]])
|
||||
if self._initOpts:
|
||||
if "timeout" in self._initOpts:
|
||||
stream.append(head + ["timeout", self._name, self._opts["timeout"]])
|
||||
# cInfo
|
||||
for p in self._initOpts:
|
||||
stream.append(head + ["setcinfo", self._name, p, self._initOpts[p]])
|
||||
stream.append(head + [p, self._initOpts[p]])
|
||||
|
||||
return stream
|
||||
|
|
|
@ -23,7 +23,7 @@ __license__ = "GPL"
|
|||
|
||||
import logging
|
||||
|
||||
from fail2ban.exceptions import UnknownJailException, DuplicateJailException
|
||||
from ..exceptions import UnknownJailException, DuplicateJailException
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
@ -165,7 +165,23 @@ class Beautifier:
|
|||
msg = "No actions for jail %s" % inC[1]
|
||||
else:
|
||||
msg = "The jail %s has the following actions:\n" % inC[1]
|
||||
msg += ", ".join(action.getName() for action in response)
|
||||
msg += ", ".join(response)
|
||||
elif inC[2] == "actionproperties":
|
||||
if len(response) == 0:
|
||||
msg = "No properties for jail %s action %s" % (
|
||||
inC[1], inC[3])
|
||||
else:
|
||||
msg = "The jail %s action %s has the following " \
|
||||
"properties:\n" % (inC[1], inC[3])
|
||||
msg += ", ".join(response)
|
||||
elif inC[2] == "actionmethods":
|
||||
if len(response) == 0:
|
||||
msg = "No methods for jail %s action %s" % (
|
||||
inC[1], inC[3])
|
||||
else:
|
||||
msg = "The jail %s action %s has the following " \
|
||||
"methods:\n" % (inC[1], inC[3])
|
||||
msg += ", ".join(response)
|
||||
except Exception:
|
||||
logSys.warning("Beautifier error. Please report the error")
|
||||
logSys.error("Beautify " + `response` + " with " + `self.__inputCmd` +
|
||||
|
|
|
@ -25,9 +25,10 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
|||
__license__ = "GPL"
|
||||
|
||||
import glob, logging, os
|
||||
from configparserinc import SafeConfigParserWithIncludes
|
||||
from ConfigParser import NoOptionError, NoSectionError
|
||||
|
||||
from .configparserinc import SafeConfigParserWithIncludes
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
||||
|
@ -158,6 +159,10 @@ class DefinitionInitConfigReader(ConfigReader):
|
|||
def read(self):
|
||||
return ConfigReader.read(self, self._file)
|
||||
|
||||
# needed for fail2ban-regex that doesn't need fancy directories
|
||||
def readexplicit(self):
|
||||
return SafeConfigParserWithIncludes.read(self, self._file)
|
||||
|
||||
def getOptions(self, pOpts):
|
||||
self._opts = ConfigReader.getOptions(
|
||||
self, "Definition", self._configOpts, pOpts)
|
||||
|
|
|
@ -25,9 +25,10 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
|||
__license__ = "GPL"
|
||||
|
||||
import logging
|
||||
from configreader import ConfigReader
|
||||
from fail2banreader import Fail2banReader
|
||||
from jailsreader import JailsReader
|
||||
|
||||
from .configreader import ConfigReader
|
||||
from .fail2banreader import Fail2banReader
|
||||
from .jailsreader import JailsReader
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
|
|
@ -25,7 +25,8 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
|||
__license__ = "GPL"
|
||||
|
||||
import logging
|
||||
from configreader import ConfigReader
|
||||
|
||||
from .configreader import ConfigReader
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
|
|
@ -25,8 +25,9 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
|||
__license__ = "GPL"
|
||||
|
||||
import logging, os, shlex
|
||||
from configreader import ConfigReader, DefinitionInitConfigReader
|
||||
from fail2ban.server.action import Action
|
||||
|
||||
from .configreader import ConfigReader, DefinitionInitConfigReader
|
||||
from ..server.action import CommandAction
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
@ -44,7 +45,7 @@ class FilterReader(DefinitionInitConfigReader):
|
|||
def convert(self):
|
||||
stream = list()
|
||||
combinedopts = dict(list(self._opts.items()) + list(self._initOpts.items()))
|
||||
opts = Action.substituteRecursiveTags(combinedopts)
|
||||
opts = CommandAction.substituteRecursiveTags(combinedopts)
|
||||
if not opts:
|
||||
raise ValueError('recursive tag definitions unable to be resolved')
|
||||
for opt, value in opts.iteritems():
|
||||
|
|
|
@ -25,10 +25,11 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
|||
__license__ = "GPL"
|
||||
|
||||
import logging, re, glob, os.path
|
||||
import json
|
||||
|
||||
from configreader import ConfigReader
|
||||
from filterreader import FilterReader
|
||||
from actionreader import ActionReader
|
||||
from .configreader import ConfigReader
|
||||
from .filterreader import FilterReader
|
||||
from .actionreader import ActionReader
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
@ -120,8 +121,20 @@ class JailReader(ConfigReader):
|
|||
if not act: # skip empty actions
|
||||
continue
|
||||
actName, actOpt = JailReader.extractOptions(act)
|
||||
if actName.endswith(".py"):
|
||||
self.__actions.append([
|
||||
"set",
|
||||
self.__name,
|
||||
"addaction",
|
||||
actOpt.pop("actname", os.path.splitext(actName)[0]),
|
||||
os.path.join(
|
||||
self.getBaseDir(), "action.d", actName),
|
||||
json.dumps(actOpt),
|
||||
])
|
||||
else:
|
||||
action = ActionReader(
|
||||
actName, self.__name, actOpt, basedir=self.getBaseDir())
|
||||
actName, self.__name, actOpt,
|
||||
basedir=self.getBaseDir())
|
||||
ret = action.read()
|
||||
if ret:
|
||||
action.getOptions(self.__opts)
|
||||
|
@ -153,7 +166,7 @@ class JailReader(ConfigReader):
|
|||
found_files = 0
|
||||
for path in self.__opts[opt].split("\n"):
|
||||
path = path.rsplit(" ", 1)
|
||||
path, tail = path if len(path) > 1 else (path[0], "false")
|
||||
path, tail = path if len(path) > 1 else (path[0], "head")
|
||||
pathList = JailReader._glob(path)
|
||||
if len(pathList) == 0:
|
||||
logSys.error("No file(s) found for glob %s" % path)
|
||||
|
@ -193,7 +206,10 @@ class JailReader(ConfigReader):
|
|||
if self.__filter:
|
||||
stream.extend(self.__filter.convert())
|
||||
for action in self.__actions:
|
||||
if isinstance(action, ConfigReader):
|
||||
stream.extend(action.convert())
|
||||
else:
|
||||
stream.append(action)
|
||||
stream.insert(0, ["add", self.__name, backend])
|
||||
return stream
|
||||
|
||||
|
|
|
@ -25,8 +25,9 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
|||
__license__ = "GPL"
|
||||
|
||||
import logging
|
||||
from configreader import ConfigReader
|
||||
from jailreader import JailReader
|
||||
|
||||
from .configreader import ConfigReader
|
||||
from .jailreader import JailReader
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
|
|
@ -29,7 +29,7 @@ __license__ = "GPL"
|
|||
class DuplicateJailException(Exception):
|
||||
pass
|
||||
|
||||
class UnknownJailException(Exception):
|
||||
class UnknownJailException(KeyError):
|
||||
pass
|
||||
|
||||
|
||||
|
|
|
@ -76,19 +76,21 @@ protocol = [
|
|||
["set <JAIL> unbanip <IP>", "manually Unban <IP> in <JAIL>"],
|
||||
["set <JAIL> maxretry <RETRY>", "sets the number of failures <RETRY> before banning the host for <JAIL>"],
|
||||
["set <JAIL> maxlines <LINES>", "sets the number of <LINES> to buffer for regex search for <JAIL>"],
|
||||
["set <JAIL> addaction <ACT>", "adds a new action named <NAME> for <JAIL>"],
|
||||
["set <JAIL> delaction <ACT>", "removes the action <NAME> from <JAIL>"],
|
||||
["set <JAIL> setcinfo <ACT> <KEY> <VALUE>", "sets <VALUE> for <KEY> of the action <NAME> for <JAIL>"],
|
||||
["set <JAIL> delcinfo <ACT> <KEY>", "removes <KEY> for the action <NAME> for <JAIL>"],
|
||||
["set <JAIL> timeout <ACT> <TIMEOUT>", "sets <TIMEOUT> as the command timeout in seconds for the action <ACT> for <JAIL>"],
|
||||
["set <JAIL> actionstart <ACT> <CMD>", "sets the start command <CMD> of the action <ACT> for <JAIL>"],
|
||||
["set <JAIL> actionstop <ACT> <CMD>", "sets the stop command <CMD> of the action <ACT> for <JAIL>"],
|
||||
["set <JAIL> actioncheck <ACT> <CMD>", "sets the check command <CMD> of the action <ACT> for <JAIL>"],
|
||||
["set <JAIL> actionban <ACT> <CMD>", "sets the ban command <CMD> of the action <ACT> for <JAIL>"],
|
||||
["set <JAIL> actionunban <ACT> <CMD>", "sets the unban command <CMD> of the action <ACT> for <JAIL>"],
|
||||
["set <JAIL> addaction <ACT>[ <PYTHONFILE> <JSONKWARGS>]", "adds a new action named <NAME> for <JAIL>. Optionally for a Python based action, a <PYTHONFILE> and <JSONKWARGS> can be specified, else will be a Command Action"],
|
||||
["set <JAIL> delaction <ACT>", "removes the action <ACT> from <JAIL>"],
|
||||
["", "COMMAND ACTION CONFIGURATION", ""],
|
||||
["set <JAIL> action <ACT> actionstart <CMD>", "sets the start command <CMD> of the action <ACT> for <JAIL>"],
|
||||
["set <JAIL> action <ACT> actionstop <CMD>", "sets the stop command <CMD> of the action <ACT> for <JAIL>"],
|
||||
["set <JAIL> action <ACT> actioncheck <CMD>", "sets the check command <CMD> of the action <ACT> for <JAIL>"],
|
||||
["set <JAIL> action <ACT> actionban <CMD>", "sets the ban command <CMD> of the action <ACT> for <JAIL>"],
|
||||
["set <JAIL> action <ACT> actionunban <CMD>", "sets the unban command <CMD> of the action <ACT> for <JAIL>"],
|
||||
["set <JAIL> action <ACT> timeout <TIMEOUT>", "sets <TIMEOUT> as the command timeout in seconds for the action <ACT> for <JAIL>"],
|
||||
["", "GENERAL ACTION CONFIGURATION", ""],
|
||||
["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> logpath", "gets the list of the monitored files for <JAIL>"],
|
||||
["get <JAIL> logencoding <ENCODING>", "gets the <ENCODING> of the log 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>"],
|
||||
["get <JAIL> ignoreip", "gets the list of ignored IP addresses for <JAIL>"],
|
||||
["get <JAIL> ignorecommand", "gets ignorecommand of <JAIL>"],
|
||||
|
@ -100,15 +102,18 @@ protocol = [
|
|||
["get <JAIL> usedns", "gets the usedns setting for <JAIL>"],
|
||||
["get <JAIL> maxretry", "gets the number of failures allowed for <JAIL>"],
|
||||
["get <JAIL> maxlines", "gets the number of lines to buffer for <JAIL>"],
|
||||
["get <JAIL> addaction", "gets the last action which has been added for <JAIL>"],
|
||||
["get <JAIL> actions", "gets a list of actions for <JAIL>"],
|
||||
["get <JAIL> actionstart <ACT>", "gets the start command for the action <ACT> for <JAIL>"],
|
||||
["get <JAIL> actionstop <ACT>", "gets the stop command for the action <ACT> for <JAIL>"],
|
||||
["get <JAIL> actioncheck <ACT>", "gets the check command for the action <ACT> for <JAIL>"],
|
||||
["get <JAIL> actionban <ACT>", "gets the ban command for the action <ACT> for <JAIL>"],
|
||||
["get <JAIL> actionunban <ACT>", "gets the unban command for the action <ACT> for <JAIL>"],
|
||||
["get <JAIL> cinfo <ACT> <KEY>", "gets the value for <KEY> for the action <ACT> for <JAIL>"],
|
||||
["get <JAIL> timeout <ACT>", "gets the command timeout in seconds for the action <ACT> for <JAIL>"],
|
||||
["", "COMMAND ACTION INFORMATION",""],
|
||||
["get <JAIL> action <ACT> actionstart", "gets the start command for the action <ACT> for <JAIL>"],
|
||||
["get <JAIL> action <ACT> actionstop", "gets the stop command for the action <ACT> for <JAIL>"],
|
||||
["get <JAIL> action <ACT> actioncheck", "gets the check command for the action <ACT> for <JAIL>"],
|
||||
["get <JAIL> action <ACT> actionban", "gets the ban command for the action <ACT> for <JAIL>"],
|
||||
["get <JAIL> action <ACT> actionunban", "gets the unban command for the action <ACT> for <JAIL>"],
|
||||
["get <JAIL> action <ACT> timeout", "gets the command timeout in seconds for the action <ACT> for <JAIL>"],
|
||||
["", "GENERAL ACTION INFORMATION", ""],
|
||||
["get <JAIL> actionproperties <ACT>", "gets a list of properties for the action <ACT> for <JAIL>"],
|
||||
["get <JAIL> actionmethods <ACT>", "gets a list of methods for the action <ACT> for <JAIL>"],
|
||||
["get <JAIL> action <ACT> <PROPERTY>", "gets the value of <PROPERTY> for the action <ACT> for <JAIL>"],
|
||||
]
|
||||
|
||||
##
|
||||
|
@ -125,12 +130,14 @@ def printFormatted():
|
|||
print
|
||||
firstHeading = True
|
||||
first = True
|
||||
for n in textwrap.wrap(m[1], WIDTH):
|
||||
if len(m[0]) >= MARGIN:
|
||||
m[1] = ' ' * WIDTH + m[1]
|
||||
for n in textwrap.wrap(m[1], WIDTH, drop_whitespace=False):
|
||||
if first:
|
||||
line = ' ' * INDENT + m[0] + ' ' * (MARGIN - len(m[0])) + n
|
||||
line = ' ' * INDENT + m[0] + ' ' * (MARGIN - len(m[0])) + n.strip()
|
||||
first = False
|
||||
else:
|
||||
line = ' ' * (INDENT + MARGIN) + n
|
||||
line = ' ' * (INDENT + MARGIN) + n.strip()
|
||||
print line
|
||||
|
||||
##
|
||||
|
|
|
@ -23,6 +23,8 @@ __license__ = "GPL"
|
|||
|
||||
import logging, os, subprocess, time, signal, tempfile
|
||||
import threading, re
|
||||
from abc import ABCMeta
|
||||
from collections import MutableMapping
|
||||
#from subprocess import call
|
||||
|
||||
# Gets the instance of the logger.
|
||||
|
@ -46,232 +48,326 @@ _RETCODE_HINTS = {
|
|||
signame = dict((num, name)
|
||||
for name, num in signal.__dict__.iteritems() if name.startswith("SIG"))
|
||||
|
||||
##
|
||||
# Execute commands.
|
||||
#
|
||||
# This class reads the failures from the Jail queue and decide if an
|
||||
# action has to be taken. A BanManager take care of the banned IP
|
||||
# addresses.
|
||||
class CallingMap(MutableMapping):
|
||||
"""A Mapping type which returns the result of callable values.
|
||||
|
||||
class Action:
|
||||
`CallingMap` behaves similar to a standard python dictionary,
|
||||
with the exception that any values which are callable, are called
|
||||
and the result is returned as the value.
|
||||
No error handling is in place, such that any errors raised in the
|
||||
callable will raised as usual.
|
||||
Actual dictionary is stored in property `data`, and can be accessed
|
||||
to obtain original callable values.
|
||||
|
||||
def __init__(self, name):
|
||||
self.__name = name
|
||||
self.__timeout = 60
|
||||
self.__cInfo = dict()
|
||||
## Command executed in order to initialize the system.
|
||||
self.__actionStart = ''
|
||||
## Command executed when an IP address gets banned.
|
||||
self.__actionBan = ''
|
||||
## Command executed when an IP address gets removed.
|
||||
self.__actionUnban = ''
|
||||
## Command executed in order to check requirements.
|
||||
self.__actionCheck = ''
|
||||
## Command executed in order to stop the system.
|
||||
self.__actionStop = ''
|
||||
logSys.debug("Created Action")
|
||||
Attributes
|
||||
----------
|
||||
data : dict
|
||||
The dictionary data which can be accessed to obtain items
|
||||
without callable values being called.
|
||||
|
||||
##
|
||||
# Sets the action name.
|
||||
#
|
||||
# @param name the name of the action
|
||||
"""
|
||||
|
||||
def setName(self, name):
|
||||
self.__name = name
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.data = dict(*args, **kwargs)
|
||||
|
||||
##
|
||||
# Returns the action name.
|
||||
#
|
||||
# @return the name of the action
|
||||
def __getitem__(self, key):
|
||||
value = self.data[key]
|
||||
if callable(value):
|
||||
return value()
|
||||
else:
|
||||
return value
|
||||
|
||||
def getName(self):
|
||||
return self.__name
|
||||
def __setitem__(self, key, value):
|
||||
self.data[key] = value
|
||||
|
||||
##
|
||||
# Sets the timeout period for commands.
|
||||
#
|
||||
# @param timeout timeout period in seconds
|
||||
def __delitem__(self, key):
|
||||
del self.data[key]
|
||||
|
||||
def setTimeout(self, timeout):
|
||||
self.__timeout = int(timeout)
|
||||
logSys.debug("Set action %s timeout = %i" % (self.__name, timeout))
|
||||
def __iter__(self):
|
||||
return iter(self.data)
|
||||
|
||||
##
|
||||
# Returns the action timeout period for commands.
|
||||
#
|
||||
# @return the timeout period in seconds
|
||||
def __len__(self):
|
||||
return len(self.data)
|
||||
|
||||
def getTimeout(self):
|
||||
return self.__timeout
|
||||
class ActionBase(object):
|
||||
"""An abstract base class for actions in Fail2Ban.
|
||||
|
||||
##
|
||||
# Sets a "CInfo".
|
||||
#
|
||||
# CInfo are statically defined properties. They can be definied by
|
||||
# the user and are used to set e-mail addresses, port, host or
|
||||
# anything that should not change during the life of the server.
|
||||
#
|
||||
# @param key the property name
|
||||
# @param value the property value
|
||||
Action Base is a base definition of what methods need to be in
|
||||
place to create a Python based action for Fail2Ban. This class can
|
||||
be inherited from to ease implementation.
|
||||
Required methods:
|
||||
- __init__(jail, name)
|
||||
- start()
|
||||
- stop()
|
||||
- ban(aInfo)
|
||||
- unban(aInfo)
|
||||
"""
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
def setCInfo(self, key, value):
|
||||
self.__cInfo[key] = value
|
||||
|
||||
##
|
||||
# Returns a "CInfo".
|
||||
#
|
||||
# @param key the property name
|
||||
|
||||
def getCInfo(self, key):
|
||||
return self.__cInfo[key]
|
||||
|
||||
##
|
||||
# Removes a "CInfo".
|
||||
#
|
||||
# @param key the property name
|
||||
|
||||
def delCInfo(self, key):
|
||||
del self.__cInfo[key]
|
||||
|
||||
##
|
||||
# Set the "start" command.
|
||||
#
|
||||
# @param value the command
|
||||
|
||||
def setActionStart(self, value):
|
||||
self.__actionStart = value
|
||||
logSys.debug("Set actionStart = %s" % value)
|
||||
|
||||
##
|
||||
# Get the "start" command.
|
||||
#
|
||||
# @return the command
|
||||
|
||||
def getActionStart(self):
|
||||
return self.__actionStart
|
||||
|
||||
##
|
||||
# Executes the action "start" command.
|
||||
#
|
||||
# Replaces the tags in the action command with value of "cInfo"
|
||||
# and executes the resulting command.
|
||||
#
|
||||
# @return True if the command succeeded
|
||||
|
||||
def execActionStart(self):
|
||||
if self.__cInfo:
|
||||
if not Action.substituteRecursiveTags(self.__cInfo):
|
||||
logSys.error("Cinfo/definitions contain self referencing definitions and cannot be resolved")
|
||||
@classmethod
|
||||
def __subclasshook__(cls, C):
|
||||
required = (
|
||||
"start",
|
||||
"stop",
|
||||
"ban",
|
||||
"unban",
|
||||
)
|
||||
for method in required:
|
||||
if not callable(getattr(C, method, None)):
|
||||
return False
|
||||
startCmd = Action.replaceTag(self.__actionStart, self.__cInfo)
|
||||
return Action.executeCmd(startCmd, self.__timeout)
|
||||
return True
|
||||
|
||||
##
|
||||
# Set the "ban" command.
|
||||
#
|
||||
# @param value the command
|
||||
def __init__(self, jail, name):
|
||||
"""Initialise action.
|
||||
|
||||
def setActionBan(self, value):
|
||||
self.__actionBan = value
|
||||
logSys.debug("Set actionBan = %s" % value)
|
||||
Called when action is created, but before the jail/actions is
|
||||
started. This should carry out necessary methods to initialise
|
||||
the action but not "start" the action.
|
||||
|
||||
##
|
||||
# Get the "ban" command.
|
||||
#
|
||||
# @return the command
|
||||
Parameters
|
||||
----------
|
||||
jail : Jail
|
||||
The jail in which the action belongs to.
|
||||
name : str
|
||||
Name assigned to the action.
|
||||
|
||||
def getActionBan(self):
|
||||
return self.__actionBan
|
||||
Notes
|
||||
-----
|
||||
Any additional arguments specified in `jail.conf` or passed
|
||||
via `fail2ban-client` will be passed as keyword arguments.
|
||||
"""
|
||||
self._jail = jail
|
||||
self._name = name
|
||||
self._logSys = logging.getLogger(
|
||||
'%s.%s' % (__name__, self.__class__.__name__))
|
||||
|
||||
##
|
||||
# Executes the action "ban" command.
|
||||
#
|
||||
# @return True if the command succeeded
|
||||
def start(self):
|
||||
"""Executed when the jail/action is started.
|
||||
"""
|
||||
pass
|
||||
|
||||
def execActionBan(self, aInfo):
|
||||
return self.__processCmd(self.__actionBan, aInfo)
|
||||
def stop(self):
|
||||
"""Executed when the jail/action is stopped.
|
||||
"""
|
||||
pass
|
||||
|
||||
##
|
||||
# Set the "unban" command.
|
||||
#
|
||||
# @param value the command
|
||||
def ban(self, aInfo):
|
||||
"""Executed when a ban occurs.
|
||||
|
||||
def setActionUnban(self, value):
|
||||
self.__actionUnban = value
|
||||
logSys.debug("Set actionUnban = %s" % value)
|
||||
Parameters
|
||||
----------
|
||||
aInfo : dict
|
||||
Dictionary which includes information in relation to
|
||||
the ban.
|
||||
"""
|
||||
pass
|
||||
|
||||
##
|
||||
# Get the "unban" command.
|
||||
#
|
||||
# @return the command
|
||||
def unban(self, aInfo):
|
||||
"""Executed when a ban expires.
|
||||
|
||||
def getActionUnban(self):
|
||||
return self.__actionUnban
|
||||
Parameters
|
||||
----------
|
||||
aInfo : dict
|
||||
Dictionary which includes information in relation to
|
||||
the ban.
|
||||
"""
|
||||
pass
|
||||
|
||||
##
|
||||
# Executes the action "unban" command.
|
||||
#
|
||||
# @return True if the command succeeded
|
||||
class CommandAction(ActionBase):
|
||||
"""A action which executes OS shell commands.
|
||||
|
||||
def execActionUnban(self, aInfo):
|
||||
return self.__processCmd(self.__actionUnban, aInfo)
|
||||
This is the default type of action which Fail2Ban uses.
|
||||
"""
|
||||
|
||||
##
|
||||
# Set the "check" command.
|
||||
#
|
||||
# @param value the command
|
||||
def __init__(self, jail, name):
|
||||
"""Initialise action.
|
||||
|
||||
def setActionCheck(self, value):
|
||||
self.__actionCheck = value
|
||||
logSys.debug("Set actionCheck = %s" % value)
|
||||
Default sets all commands for actions as empty string, such
|
||||
no command is executed.
|
||||
|
||||
##
|
||||
# Get the "check" command.
|
||||
#
|
||||
# @return the command
|
||||
Parameters
|
||||
----------
|
||||
jail : Jail
|
||||
The jail in which the action belongs to.
|
||||
name : str
|
||||
Name assigned to the action.
|
||||
"""
|
||||
|
||||
def getActionCheck(self):
|
||||
return self.__actionCheck
|
||||
super(CommandAction, self).__init__(jail, name)
|
||||
self.timeout = 60
|
||||
## Command executed in order to initialize the system.
|
||||
self.actionstart = ''
|
||||
## Command executed when an IP address gets banned.
|
||||
self.actionban = ''
|
||||
## Command executed when an IP address gets removed.
|
||||
self.actionunban = ''
|
||||
## Command executed in order to check requirements.
|
||||
self.actioncheck = ''
|
||||
## Command executed in order to stop the system.
|
||||
self.actionstop = ''
|
||||
self._logSys.debug("Created %s" % self.__class__)
|
||||
|
||||
##
|
||||
# Set the "stop" command.
|
||||
#
|
||||
# @param value the command
|
||||
@classmethod
|
||||
def __subclasshook__(cls, C):
|
||||
return NotImplemented # Standard checks
|
||||
|
||||
def setActionStop(self, value):
|
||||
self.__actionStop = value
|
||||
logSys.debug("Set actionStop = %s" % value)
|
||||
@property
|
||||
def timeout(self):
|
||||
"""Time out period in seconds for execution of commands.
|
||||
"""
|
||||
return self._timeout
|
||||
|
||||
##
|
||||
# Get the "stop" command.
|
||||
#
|
||||
# @return the command
|
||||
@timeout.setter
|
||||
def timeout(self, timeout):
|
||||
self._timeout = int(timeout)
|
||||
self._logSys.debug("Set action %s timeout = %i" %
|
||||
(self._name, self.timeout))
|
||||
|
||||
def getActionStop(self):
|
||||
return self.__actionStop
|
||||
@property
|
||||
def _properties(self):
|
||||
"""A dictionary of the actions properties.
|
||||
|
||||
##
|
||||
# Executes the action "stop" command.
|
||||
#
|
||||
# Replaces the tags in the action command with value of "cInfo"
|
||||
# and executes the resulting command.
|
||||
#
|
||||
# @return True if the command succeeded
|
||||
This is used to subsitute "tags" in the commands.
|
||||
"""
|
||||
return dict(
|
||||
(key, getattr(self, key))
|
||||
for key in dir(self)
|
||||
if not key.startswith("_") and not callable(getattr(self, key)))
|
||||
|
||||
def execActionStop(self):
|
||||
stopCmd = Action.replaceTag(self.__actionStop, self.__cInfo)
|
||||
return Action.executeCmd(stopCmd, self.__timeout)
|
||||
@property
|
||||
def actionstart(self):
|
||||
"""The command executed on start of the jail/action.
|
||||
"""
|
||||
return self._actionstart
|
||||
|
||||
##
|
||||
# Sort out tag definitions within other tags
|
||||
#
|
||||
# so: becomes:
|
||||
# a = 3 a = 3
|
||||
# b = <a>_3 b = 3_3
|
||||
# @param tags, a dictionary
|
||||
# @returns tags altered or False if there is a recursive definition
|
||||
#@staticmethod
|
||||
@actionstart.setter
|
||||
def actionstart(self, value):
|
||||
self._actionstart = value
|
||||
self._logSys.debug("Set actionstart = %s" % value)
|
||||
|
||||
def start(self):
|
||||
"""Executes the "actionstart" command.
|
||||
|
||||
Replace the tags in the action command with actions properties
|
||||
and executes the resulting command.
|
||||
"""
|
||||
if (self._properties and
|
||||
not self.substituteRecursiveTags(self._properties)):
|
||||
self._logSys.error(
|
||||
"properties contain self referencing definitions "
|
||||
"and cannot be resolved")
|
||||
raise RuntimeError("Error starting action")
|
||||
startCmd = self.replaceTag(self.actionstart, self._properties)
|
||||
if not self.executeCmd(startCmd, self.timeout):
|
||||
raise RuntimeError("Error starting action")
|
||||
|
||||
@property
|
||||
def actionban(self):
|
||||
"""The command used when a ban occurs.
|
||||
"""
|
||||
return self._actionban
|
||||
|
||||
@actionban.setter
|
||||
def actionban(self, value):
|
||||
self._actionban = value
|
||||
self._logSys.debug("Set actionban = %s" % value)
|
||||
|
||||
def ban(self, aInfo):
|
||||
"""Executes the "actionban" command.
|
||||
|
||||
Replaces the tags in the action command with actions properties
|
||||
and ban information, and executes the resulting command.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
aInfo : dict
|
||||
Dictionary which includes information in relation to
|
||||
the ban.
|
||||
"""
|
||||
if not self._processCmd(self.actionban, aInfo):
|
||||
raise RuntimeError("Error banning %(ip)s" % aInfo)
|
||||
|
||||
@property
|
||||
def actionunban(self):
|
||||
"""The command used when an unban occurs.
|
||||
"""
|
||||
return self._actionunban
|
||||
|
||||
@actionunban.setter
|
||||
def actionunban(self, value):
|
||||
self._actionunban = value
|
||||
self._logSys.debug("Set actionunban = %s" % value)
|
||||
|
||||
def unban(self, aInfo):
|
||||
"""Executes the "actionunban" command.
|
||||
|
||||
Replaces the tags in the action command with actions properties
|
||||
and ban information, and executes the resulting command.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
aInfo : dict
|
||||
Dictionary which includes information in relation to
|
||||
the ban.
|
||||
"""
|
||||
if not self._processCmd(self.actionunban, aInfo):
|
||||
raise RuntimeError("Error unbanning %(ip)s" % aInfo)
|
||||
|
||||
@property
|
||||
def actioncheck(self):
|
||||
"""The command used to check the environment.
|
||||
|
||||
This is used prior to a ban taking place to ensure the
|
||||
environment is appropriate. If this check fails, `stop` and
|
||||
`start` is executed prior to the check being called again.
|
||||
"""
|
||||
return self._actioncheck
|
||||
|
||||
@actioncheck.setter
|
||||
def actioncheck(self, value):
|
||||
self._actioncheck = value
|
||||
self._logSys.debug("Set actioncheck = %s" % value)
|
||||
|
||||
@property
|
||||
def actionstop(self):
|
||||
"""The command executed when the jail/actions stops.
|
||||
"""
|
||||
return self._actionstop
|
||||
|
||||
@actionstop.setter
|
||||
def actionstop(self, value):
|
||||
self._actionstop = value
|
||||
self._logSys.debug("Set actionstop = %s" % value)
|
||||
|
||||
def stop(self):
|
||||
"""Executes the "actionstop" command.
|
||||
|
||||
Replaces the tags in the action command with actions properties
|
||||
and executes the resulting command.
|
||||
"""
|
||||
stopCmd = self.replaceTag(self.actionstop, self._properties)
|
||||
if not self.executeCmd(stopCmd, self.timeout):
|
||||
raise RuntimeError("Error stopping action")
|
||||
|
||||
@staticmethod
|
||||
def substituteRecursiveTags(tags):
|
||||
"""Sort out tag definitions within other tags.
|
||||
|
||||
so: becomes:
|
||||
a = 3 a = 3
|
||||
b = <a>_3 b = 3_3
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tags : dict
|
||||
Dictionary of tags(keys) and their values.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
Dictionary of tags(keys) and their values, with tags
|
||||
within the values recursively replaced.
|
||||
"""
|
||||
t = re.compile(r'<([^ >]+)>')
|
||||
for tag, value in tags.iteritems():
|
||||
value = str(value)
|
||||
|
@ -299,98 +395,130 @@ class Action:
|
|||
#logSys.log(5, 'TAG: %s, newvalue: %s' % (tag, value))
|
||||
tags[tag] = value
|
||||
return tags
|
||||
substituteRecursiveTags = staticmethod(substituteRecursiveTags)
|
||||
|
||||
#@staticmethod
|
||||
def escapeTag(tag):
|
||||
@staticmethod
|
||||
def escapeTag(value):
|
||||
"""Escape characters which may be used for command injection.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
value : str
|
||||
A string of which characters will be escaped.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
`value` with certain characters escaped.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The following characters are escaped::
|
||||
|
||||
\\#&;`|*?~<>^()[]{}$'"
|
||||
|
||||
"""
|
||||
for c in '\\#&;`|*?~<>^()[]{}$\'"':
|
||||
if c in tag:
|
||||
tag = tag.replace(c, '\\' + c)
|
||||
return tag
|
||||
escapeTag = staticmethod(escapeTag)
|
||||
if c in value:
|
||||
value = value.replace(c, '\\' + c)
|
||||
return value
|
||||
|
||||
##
|
||||
# Replaces tags in query with property values in aInfo.
|
||||
#
|
||||
# @param query the query string with tags
|
||||
# @param aInfo the properties
|
||||
# @return a string
|
||||
@classmethod
|
||||
def replaceTag(cls, query, aInfo):
|
||||
"""Replaces tags in `query` with property values.
|
||||
|
||||
#@staticmethod
|
||||
def replaceTag(query, aInfo):
|
||||
""" Replace tags in query
|
||||
Parameters
|
||||
----------
|
||||
query : str
|
||||
String with tags.
|
||||
aInfo : dict
|
||||
Tags(keys) and associated values for substitution in query.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
`query` string with tags replaced.
|
||||
"""
|
||||
string = query
|
||||
for tag, value in aInfo.iteritems():
|
||||
for tag in aInfo:
|
||||
if "<%s>" % tag in query:
|
||||
if callable(value):
|
||||
value = value()
|
||||
value = str(value) # assure string
|
||||
value = str(aInfo[tag]) # assure string
|
||||
if tag.endswith('matches'):
|
||||
# That one needs to be escaped since its content is
|
||||
# out of our control
|
||||
value = Action.escapeTag(value)
|
||||
value = cls.escapeTag(value)
|
||||
string = string.replace('<' + tag + '>', value)
|
||||
# New line
|
||||
string = string.replace("<br>", '\n')
|
||||
return string
|
||||
replaceTag = staticmethod(replaceTag)
|
||||
|
||||
##
|
||||
# Executes a command with preliminary checks and substitutions.
|
||||
#
|
||||
# Before executing any commands, executes the "check" command first
|
||||
# in order to check if pre-requirements are met. If this check fails,
|
||||
# it tries to restore a sane environment before executing the real
|
||||
# command.
|
||||
# Replaces "aInfo" and "cInfo" in the query too.
|
||||
#
|
||||
# @param cmd The command to execute
|
||||
# @param aInfo Dynamic properties
|
||||
# @return True if the command succeeded
|
||||
def _processCmd(self, cmd, aInfo = None):
|
||||
"""Executes a command with preliminary checks and substitutions.
|
||||
|
||||
def __processCmd(self, cmd, aInfo = None):
|
||||
""" Executes an OS command.
|
||||
Before executing any commands, executes the "check" command first
|
||||
in order to check if pre-requirements are met. If this check fails,
|
||||
it tries to restore a sane environment before executing the real
|
||||
command.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cmd : str
|
||||
The command to execute.
|
||||
aInfo : dictionary
|
||||
Dynamic properties.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the command succeeded.
|
||||
"""
|
||||
if cmd == "":
|
||||
logSys.debug("Nothing to do")
|
||||
self._logSys.debug("Nothing to do")
|
||||
return True
|
||||
|
||||
checkCmd = Action.replaceTag(self.__actionCheck, self.__cInfo)
|
||||
if not Action.executeCmd(checkCmd, self.__timeout):
|
||||
logSys.error("Invariant check failed. Trying to restore a sane" +
|
||||
" environment")
|
||||
self.execActionStop()
|
||||
self.execActionStart()
|
||||
if not Action.executeCmd(checkCmd, self.__timeout):
|
||||
logSys.fatal("Unable to restore environment")
|
||||
checkCmd = self.replaceTag(self.actioncheck, self._properties)
|
||||
if not self.executeCmd(checkCmd, self.timeout):
|
||||
self._logSys.error(
|
||||
"Invariant check failed. Trying to restore a sane environment")
|
||||
self.stop()
|
||||
self.start()
|
||||
if not self.executeCmd(checkCmd, self.timeout):
|
||||
self._logSys.fatal("Unable to restore environment")
|
||||
return False
|
||||
|
||||
# Replace tags
|
||||
if not aInfo is None:
|
||||
realCmd = Action.replaceTag(cmd, aInfo)
|
||||
realCmd = self.replaceTag(cmd, aInfo)
|
||||
else:
|
||||
realCmd = cmd
|
||||
|
||||
# Replace static fields
|
||||
realCmd = Action.replaceTag(realCmd, self.__cInfo)
|
||||
realCmd = self.replaceTag(realCmd, self._properties)
|
||||
|
||||
return Action.executeCmd(realCmd, self.__timeout)
|
||||
return self.executeCmd(realCmd, self.timeout)
|
||||
|
||||
##
|
||||
# Executes a command.
|
||||
#
|
||||
# We need a shell here because commands are mainly shell script. They
|
||||
# contain pipe, redirection, etc.
|
||||
#
|
||||
# @todo Force the use of bash!?
|
||||
# @todo Kill the command after a given timeout
|
||||
#
|
||||
# @param realCmd the command to execute
|
||||
# @return True if the command succeeded
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def executeCmd(realCmd, timeout=60):
|
||||
"""Executes a command.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
realCmd : str
|
||||
The command to execute.
|
||||
timeout : int
|
||||
The time out in seconds for the command.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the command succeeded.
|
||||
|
||||
Raises
|
||||
------
|
||||
OSError
|
||||
If command fails to be executed.
|
||||
RuntimeError
|
||||
If command execution times out.
|
||||
"""
|
||||
logSys.debug(realCmd)
|
||||
if not realCmd:
|
||||
logSys.debug("Nothing to do")
|
||||
|
@ -420,7 +548,6 @@ class Action:
|
|||
retcode = popen.poll()
|
||||
except OSError, e:
|
||||
logSys.error("%s -- failed with %s" % (realCmd, e))
|
||||
return False
|
||||
finally:
|
||||
_cmd_lock.release()
|
||||
|
||||
|
@ -448,5 +575,5 @@ class Action:
|
|||
logSys.info("HINT on %i: %s"
|
||||
% (retcode, msg % locals()))
|
||||
return False
|
||||
executeCmd = staticmethod(executeCmd)
|
||||
raise RuntimeError("Command execution failed: %s" % realCmd)
|
||||
|
||||
|
|
|
@ -24,93 +24,112 @@ __author__ = "Cyril Jaquier"
|
|||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||
__license__ = "GPL"
|
||||
|
||||
from banmanager import BanManager
|
||||
from jailthread import JailThread
|
||||
from action import Action
|
||||
from mytime import MyTime
|
||||
import time, logging
|
||||
import os
|
||||
import imp
|
||||
from collections import Mapping
|
||||
|
||||
from .banmanager import BanManager
|
||||
from .jailthread import JailThread
|
||||
from .action import ActionBase, CommandAction, CallingMap
|
||||
from .mytime import MyTime
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
||||
##
|
||||
# Execute commands.
|
||||
#
|
||||
# This class reads the failures from the Jail queue and decide if an
|
||||
# action has to be taken. A BanManager take care of the banned IP
|
||||
# addresses.
|
||||
class Actions(JailThread, Mapping):
|
||||
"""Handles jail actions.
|
||||
|
||||
class Actions(JailThread):
|
||||
|
||||
##
|
||||
# Constructor.
|
||||
#
|
||||
# Initialize the filter object with default values.
|
||||
# @param jail the jail object
|
||||
This class handles the actions of the jail. Creation, deletion or to
|
||||
actions must be done through this class. This class is based on the
|
||||
Mapping type, and the `add` method must be used to add new actions.
|
||||
This class also starts and stops the actions, and fetches bans from
|
||||
the jail executing these bans via the actions.
|
||||
"""
|
||||
|
||||
def __init__(self, jail):
|
||||
"""Initialise an empty Actions instance.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
jail: Jail
|
||||
The jail of which the actions belongs to.
|
||||
"""
|
||||
JailThread.__init__(self)
|
||||
## The jail which contains this action.
|
||||
self.jail = jail
|
||||
self.__actions = list()
|
||||
self._jail = jail
|
||||
self._actions = dict()
|
||||
## The ban manager.
|
||||
self.__banManager = BanManager()
|
||||
|
||||
##
|
||||
# Adds an action.
|
||||
#
|
||||
# @param name The action name
|
||||
def add(self, name, pythonModule=None, initOpts=None):
|
||||
"""Adds a new action.
|
||||
|
||||
def addAction(self, name):
|
||||
Add a new action if not already present, defaulting to standard
|
||||
`CommandAction`, or specified Python module.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
The name of the action.
|
||||
pythonModule : str, optional
|
||||
Path to Python file which must contain `Action` class.
|
||||
Default None, which means `CommandAction` is used.
|
||||
initOpts : dict, optional
|
||||
Options for Python Action, used as keyword arguments for
|
||||
initialisation. Default None.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If action name already exists.
|
||||
RuntimeError
|
||||
If external Python module does not have `Action` class
|
||||
or does not implement necessary methods as per `ActionBase`
|
||||
abstract class.
|
||||
"""
|
||||
# Check is action name already exists
|
||||
if name in [action.getName() for action in self.__actions]:
|
||||
if name in self._actions:
|
||||
raise ValueError("Action %s already exists" % name)
|
||||
action = Action(name)
|
||||
self.__actions.append(action)
|
||||
if pythonModule is None:
|
||||
action = CommandAction(self._jail, name)
|
||||
else:
|
||||
pythonModuleName = os.path.basename(pythonModule.strip(".py"))
|
||||
customActionModule = imp.load_source(
|
||||
pythonModuleName, pythonModule)
|
||||
if not hasattr(customActionModule, "Action"):
|
||||
raise RuntimeError(
|
||||
"%s module does not have 'Action' class" % pythonModule)
|
||||
elif not issubclass(customActionModule.Action, ActionBase):
|
||||
raise RuntimeError(
|
||||
"%s module %s does not implement required methods" % (
|
||||
pythonModule, customActionModule.Action.__name__))
|
||||
action = customActionModule.Action(self._jail, name, **initOpts)
|
||||
self._actions[name] = action
|
||||
|
||||
##
|
||||
# Removes an action.
|
||||
#
|
||||
# @param name The action name
|
||||
|
||||
def delAction(self, name):
|
||||
for action in self.__actions:
|
||||
if action.getName() == name:
|
||||
self.__actions.remove(action)
|
||||
return
|
||||
def __getitem__(self, name):
|
||||
try:
|
||||
return self._actions[name]
|
||||
except KeyError:
|
||||
raise KeyError("Invalid Action name: %s" % name)
|
||||
|
||||
##
|
||||
# Returns an action.
|
||||
#
|
||||
# Raises a KeyError exception if the action does not exist.
|
||||
#
|
||||
# @param name the action name
|
||||
# @return the action
|
||||
def __delitem__(self, name):
|
||||
try:
|
||||
del self._actions[name]
|
||||
except KeyError:
|
||||
raise KeyError("Invalid Action name: %s" % name)
|
||||
|
||||
def getAction(self, name):
|
||||
for action in self.__actions:
|
||||
if action.getName() == name:
|
||||
return action
|
||||
raise KeyError("Invalid Action name")
|
||||
def __iter__(self):
|
||||
return iter(self._actions)
|
||||
|
||||
##
|
||||
# Returns the last defined action.
|
||||
#
|
||||
# @return The last defined action.
|
||||
def __len__(self):
|
||||
return len(self._actions)
|
||||
|
||||
def getLastAction(self):
|
||||
action = self.__actions.pop()
|
||||
self.__actions.append(action)
|
||||
return action
|
||||
def __eq__(self, other): # Required for Threading
|
||||
return False
|
||||
|
||||
##
|
||||
# Returns the list of actions
|
||||
#
|
||||
# @return list of actions
|
||||
|
||||
def getActions(self):
|
||||
return self.__actions
|
||||
def __hash__(self): # Required for Threading
|
||||
return id(self)
|
||||
|
||||
##
|
||||
# Set the ban time.
|
||||
|
@ -129,33 +148,51 @@ class Actions(JailThread):
|
|||
def getBanTime(self):
|
||||
return self.__banManager.getBanTime()
|
||||
|
||||
##
|
||||
# Remove a banned IP now, rather than waiting for it to expire, even if set to never expire.
|
||||
#
|
||||
# @return the IP string or 'None' if not unbanned.
|
||||
def removeBannedIP(self, ip):
|
||||
"""Removes banned IP calling actions' unban method
|
||||
|
||||
Remove a banned IP now, rather than waiting for it to expire,
|
||||
even if set to never expire.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ip : str
|
||||
The IP address to unban
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If `ip` is not banned
|
||||
"""
|
||||
# Find the ticket with the IP.
|
||||
ticket = self.__banManager.getTicketByIP(ip)
|
||||
if ticket is not None:
|
||||
# Unban the IP.
|
||||
self.__unBan(ticket)
|
||||
return ip
|
||||
else:
|
||||
raise ValueError("IP %s is not banned" % ip)
|
||||
|
||||
##
|
||||
# Main loop.
|
||||
#
|
||||
# This function is the main loop of the thread. It checks the Jail
|
||||
# queue and executes commands when an IP address is banned.
|
||||
# @return True when the thread exits nicely
|
||||
|
||||
def run(self):
|
||||
"""Main loop for Threading.
|
||||
|
||||
This function is the main loop of the thread. It checks the jail
|
||||
queue and executes commands when an IP address is banned.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True when the thread exits nicely.
|
||||
"""
|
||||
self.setActive(True)
|
||||
for action in self.__actions:
|
||||
action.execActionStart()
|
||||
for name, action in self._actions.iteritems():
|
||||
try:
|
||||
action.start()
|
||||
except Exception as e:
|
||||
logSys.error("Failed to start jail '%s' action '%s': %s",
|
||||
self._jail.getName(), name, e)
|
||||
while self._isActive():
|
||||
if not self.getIdle():
|
||||
#logSys.debug(self.jail.getName() + ": action")
|
||||
#logSys.debug(self._jail.getName() + ": action")
|
||||
ret = self.__checkBan()
|
||||
if not ret:
|
||||
self.__checkUnBan()
|
||||
|
@ -163,94 +200,116 @@ class Actions(JailThread):
|
|||
else:
|
||||
time.sleep(self.getSleepTime())
|
||||
self.__flushBan()
|
||||
for action in self.__actions:
|
||||
action.execActionStop()
|
||||
logSys.debug(self.jail.getName() + ": action terminated")
|
||||
for name, action in self._actions.iteritems():
|
||||
try:
|
||||
action.stop()
|
||||
except Exception as e:
|
||||
logSys.error("Failed to stop jail '%s' action '%s': %s",
|
||||
self._jail.getName(), name, e)
|
||||
logSys.debug(self._jail.getName() + ": action terminated")
|
||||
return True
|
||||
|
||||
##
|
||||
# Check for IP address to ban.
|
||||
#
|
||||
# Look in the Jail queue for FailTicket. If a ticket is available,
|
||||
# it executes the "ban" command and add a ticket to the BanManager.
|
||||
# @return True if an IP address get banned
|
||||
|
||||
def __checkBan(self):
|
||||
ticket = self.jail.getFailTicket()
|
||||
"""Check for IP address to ban.
|
||||
|
||||
Look in the jail queue for FailTicket. If a ticket is available,
|
||||
it executes the "ban" command and adds a ticket to the BanManager.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if an IP address get banned.
|
||||
"""
|
||||
ticket = self._jail.getFailTicket()
|
||||
if ticket != False:
|
||||
aInfo = dict()
|
||||
aInfo = CallingMap()
|
||||
bTicket = BanManager.createBanTicket(ticket)
|
||||
aInfo["ip"] = bTicket.getIP()
|
||||
aInfo["failures"] = bTicket.getAttempt()
|
||||
aInfo["time"] = bTicket.getTime()
|
||||
aInfo["matches"] = "\n".join(bTicket.getMatches())
|
||||
if self.jail.getDatabase() is not None:
|
||||
if self._jail.getDatabase() is not None:
|
||||
aInfo["ipmatches"] = lambda: "\n".join(
|
||||
self.jail.getDatabase().getBansMerged(
|
||||
self._jail.getDatabase().getBansMerged(
|
||||
ip=bTicket.getIP()).getMatches())
|
||||
aInfo["ipjailmatches"] = lambda: "\n".join(
|
||||
self.jail.getDatabase().getBansMerged(
|
||||
ip=bTicket.getIP(), jail=self.jail).getMatches())
|
||||
self._jail.getDatabase().getBansMerged(
|
||||
ip=bTicket.getIP(), jail=self._jail).getMatches())
|
||||
aInfo["ipfailures"] = lambda: "\n".join(
|
||||
self.jail.getDatabase().getBansMerged(
|
||||
self._jail.getDatabase().getBansMerged(
|
||||
ip=bTicket.getIP()).getAttempt())
|
||||
aInfo["ipjailfailures"] = lambda: "\n".join(
|
||||
self.jail.getDatabase().getBansMerged(
|
||||
ip=bTicket.getIP(), jail=self.jail).getAttempt())
|
||||
self._jail.getDatabase().getBansMerged(
|
||||
ip=bTicket.getIP(), jail=self._jail).getAttempt())
|
||||
if self.__banManager.addBanTicket(bTicket):
|
||||
logSys.warning("[%s] Ban %s" % (self.jail.getName(), aInfo["ip"]))
|
||||
for action in self.__actions:
|
||||
action.execActionBan(aInfo)
|
||||
logSys.warning("[%s] Ban %s" % (self._jail.getName(), aInfo["ip"]))
|
||||
for name, action in self._actions.iteritems():
|
||||
try:
|
||||
action.ban(aInfo)
|
||||
except Exception as e:
|
||||
logSys.error(
|
||||
"Failed to execute ban jail '%s' action '%s': %s",
|
||||
self._jail.getName(), name, e)
|
||||
return True
|
||||
else:
|
||||
logSys.info("[%s] %s already banned" % (self.jail.getName(),
|
||||
logSys.info("[%s] %s already banned" % (self._jail.getName(),
|
||||
aInfo["ip"]))
|
||||
return False
|
||||
|
||||
##
|
||||
# Check for IP address to unban.
|
||||
#
|
||||
# Unban IP address which are outdated.
|
||||
|
||||
def __checkUnBan(self):
|
||||
"""Check for IP address to unban.
|
||||
|
||||
Unban IP addresses which are outdated.
|
||||
"""
|
||||
for ticket in self.__banManager.unBanList(MyTime.time()):
|
||||
self.__unBan(ticket)
|
||||
|
||||
##
|
||||
# Flush the ban list.
|
||||
#
|
||||
# Unban all IP address which are still in the banning list.
|
||||
|
||||
def __flushBan(self):
|
||||
"""Flush the ban list.
|
||||
|
||||
Unban all IP address which are still in the banning list.
|
||||
"""
|
||||
logSys.debug("Flush ban list")
|
||||
for ticket in self.__banManager.flushBanList():
|
||||
self.__unBan(ticket)
|
||||
|
||||
##
|
||||
# Unbans host corresponding to the ticket.
|
||||
#
|
||||
# Executes the actions in order to unban the host given in the
|
||||
# ticket.
|
||||
|
||||
def __unBan(self, ticket):
|
||||
"""Unbans host corresponding to the ticket.
|
||||
|
||||
Executes the actions in order to unban the host given in the
|
||||
ticket.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ticket : FailTicket
|
||||
Ticket of failures of which to unban
|
||||
"""
|
||||
aInfo = dict()
|
||||
aInfo["ip"] = ticket.getIP()
|
||||
aInfo["failures"] = ticket.getAttempt()
|
||||
aInfo["time"] = ticket.getTime()
|
||||
aInfo["matches"] = "".join(ticket.getMatches())
|
||||
logSys.warning("[%s] Unban %s" % (self.jail.getName(), aInfo["ip"]))
|
||||
for action in self.__actions:
|
||||
action.execActionUnban(aInfo)
|
||||
|
||||
|
||||
##
|
||||
# Get the status of the filter.
|
||||
#
|
||||
# Get some informations about the filter state such as the total
|
||||
# number of failures.
|
||||
# @return a list with tuple
|
||||
logSys.warning("[%s] Unban %s" % (self._jail.getName(), aInfo["ip"]))
|
||||
for name, action in self._actions.iteritems():
|
||||
try:
|
||||
action.unban(aInfo)
|
||||
except Exception as e:
|
||||
logSys.error(
|
||||
"Failed to execute unban jail '%s' action '%s': %s",
|
||||
self._jail.getName(), name, e)
|
||||
|
||||
def status(self):
|
||||
"""Get the status of the filter.
|
||||
|
||||
Get some informations about the filter state such as the total
|
||||
number of failures.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
List of tuple pairs, each containing a description and value
|
||||
for general status information.
|
||||
"""
|
||||
ret = [("Currently banned", self.__banManager.size()),
|
||||
("Total banned", self.__banManager.getBanTotal()),
|
||||
("IP list", self.__banManager.getBanList())]
|
||||
|
|
|
@ -27,7 +27,7 @@ __license__ = "GPL"
|
|||
from pickle import dumps, loads, HIGHEST_PROTOCOL
|
||||
import asyncore, asynchat, socket, os, logging, sys, traceback, fcntl
|
||||
|
||||
from fail2ban import helpers
|
||||
from .. import helpers
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
@ -76,7 +76,7 @@ class RequestHandler(asynchat.async_chat):
|
|||
# Serializes the response.
|
||||
message = dumps(message, HIGHEST_PROTOCOL)
|
||||
# Sends the response to the client.
|
||||
self.send(message + RequestHandler.END_STRING)
|
||||
self.push(message + RequestHandler.END_STRING)
|
||||
# Closes the channel.
|
||||
self.close_when_done()
|
||||
|
||||
|
|
|
@ -24,10 +24,11 @@ __author__ = "Cyril Jaquier"
|
|||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||
__license__ = "GPL"
|
||||
|
||||
from ticket import BanTicket
|
||||
from threading import Lock
|
||||
from mytime import MyTime
|
||||
import logging
|
||||
from threading import Lock
|
||||
|
||||
from .ticket import BanTicket
|
||||
from .mytime import MyTime
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
|
|
@ -29,8 +29,8 @@ import json
|
|||
import locale
|
||||
from functools import wraps
|
||||
|
||||
from fail2ban.server.mytime import MyTime
|
||||
from fail2ban.server.ticket import FailTicket
|
||||
from .mytime import MyTime
|
||||
from .ticket import FailTicket
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
|
|
@ -22,10 +22,10 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
|||
__license__ = "GPL"
|
||||
|
||||
import sys, time, logging
|
||||
|
||||
from datetemplate import DatePatternRegex, DateTai64n, DateEpoch, DateISO8601
|
||||
from threading import Lock
|
||||
|
||||
from .datetemplate import DatePatternRegex, DateTai64n, DateEpoch, DateISO8601
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -25,14 +25,13 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
|||
__license__ = "GPL"
|
||||
|
||||
import re, time, calendar
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from mytime import MyTime
|
||||
import iso8601
|
||||
from .mytime import MyTime
|
||||
from . import iso8601
|
||||
|
||||
import logging
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
|
|
@ -24,11 +24,12 @@ __author__ = "Cyril Jaquier"
|
|||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||
__license__ = "GPL"
|
||||
|
||||
from faildata import FailData
|
||||
from ticket import FailTicket
|
||||
from threading import Lock
|
||||
import logging
|
||||
|
||||
from .faildata import FailData
|
||||
from .ticket import FailTicket
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -21,18 +21,17 @@ __author__ = "Cyril Jaquier and Fail2Ban Contributors"
|
|||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2013 Yaroslav Halchenko"
|
||||
__license__ = "GPL"
|
||||
|
||||
from failmanager import FailManagerEmpty
|
||||
from failmanager import FailManager
|
||||
from ticket import FailTicket
|
||||
from jailthread import JailThread
|
||||
from datedetector import DateDetector
|
||||
from datetemplate import DatePatternRegex, DateISO8601, DateEpoch, DateTai64n
|
||||
from mytime import MyTime
|
||||
from failregex import FailRegex, Regex, RegexException
|
||||
from action import Action
|
||||
|
||||
import logging, re, os, fcntl, time, sys, locale, codecs
|
||||
|
||||
from .failmanager import FailManagerEmpty, FailManager
|
||||
from .ticket import FailTicket
|
||||
from .jailthread import JailThread
|
||||
from .datedetector import DateDetector
|
||||
from .datetemplate import DatePatternRegex, DateISO8601, DateEpoch, DateTai64n
|
||||
from .mytime import MyTime
|
||||
from .failregex import FailRegex, Regex, RegexException
|
||||
from .action import CommandAction
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
||||
|
@ -378,9 +377,9 @@ class Filter(JailThread):
|
|||
return True
|
||||
|
||||
if self.__ignoreCommand:
|
||||
command = Action.replaceTag(self.__ignoreCommand, { 'ip': ip } )
|
||||
command = CommandAction.replaceTag(self.__ignoreCommand, { 'ip': ip } )
|
||||
logSys.debug('ignore command: ' + command)
|
||||
return Action.executeCmd(command)
|
||||
return CommandAction.executeCmd(command)
|
||||
|
||||
return False
|
||||
|
||||
|
|
|
@ -23,11 +23,13 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko"
|
|||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2012 Yaroslav Halchenko"
|
||||
__license__ = "GPL"
|
||||
|
||||
from failmanager import FailManagerEmpty
|
||||
from filter import FileFilter
|
||||
from mytime import MyTime
|
||||
import time, logging, fcntl
|
||||
|
||||
import time, logging, gamin, fcntl
|
||||
import gamin
|
||||
|
||||
from .failmanager import FailManagerEmpty
|
||||
from .filter import FileFilter
|
||||
from .mytime import MyTime
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
|
|
@ -24,12 +24,12 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko"
|
|||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier; 2012 Yaroslav Halchenko"
|
||||
__license__ = "GPL"
|
||||
|
||||
from failmanager import FailManagerEmpty
|
||||
from filter import FileFilter
|
||||
from mytime import MyTime
|
||||
|
||||
import time, logging, os
|
||||
|
||||
from .failmanager import FailManagerEmpty
|
||||
from .filter import FileFilter
|
||||
from .mytime import MyTime
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -24,13 +24,12 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2012 Lee Clemens, 2012 Y
|
|||
__license__ = "GPL"
|
||||
|
||||
import time, logging, pyinotify
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
from os.path import dirname, sep as pathsep
|
||||
|
||||
from failmanager import FailManagerEmpty
|
||||
from filter import FileFilter
|
||||
from mytime import MyTime
|
||||
from .failmanager import FailManagerEmpty
|
||||
from .filter import FileFilter
|
||||
from .mytime import MyTime
|
||||
|
||||
|
||||
if not hasattr(pyinotify, '__version__') \
|
||||
|
|
|
@ -29,9 +29,9 @@ from systemd import journal
|
|||
if LooseVersion(getattr(journal, '__version__', "0")) < '204':
|
||||
raise ImportError("Fail2Ban requires systemd >= 204")
|
||||
|
||||
from failmanager import FailManagerEmpty
|
||||
from filter import JournalFilter
|
||||
from mytime import MyTime
|
||||
from .failmanager import FailManagerEmpty
|
||||
from .filter import JournalFilter
|
||||
from .mytime import MyTime
|
||||
|
||||
|
||||
# Gets the instance of the logger.
|
||||
|
|
|
@ -25,7 +25,7 @@ __license__ = "GPL"
|
|||
|
||||
import Queue, logging
|
||||
|
||||
from actions import Actions
|
||||
from .actions import Actions
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
@ -71,7 +71,7 @@ class Jail:
|
|||
"%r was requested" % (b, backend))
|
||||
else:
|
||||
logSys.info("Initiated %r backend" % b)
|
||||
self.__action = Actions(self)
|
||||
self.__actions = Actions(self)
|
||||
return # we are done
|
||||
except ImportError, e:
|
||||
logSys.debug(
|
||||
|
@ -110,11 +110,10 @@ class Jail:
|
|||
self.__filter = FilterSystemd(self)
|
||||
|
||||
def setName(self, name):
|
||||
# 20 based on iptable chain name limit of 30 less len('fail2ban-')
|
||||
if len(name) >= 20:
|
||||
# 26 based on iptable chain name limit of 30 less len('f2b-')
|
||||
if len(name) >= 26:
|
||||
logSys.warning("Jail name %r might be too long and some commands "
|
||||
" (e.g. iptables) might not function correctly."
|
||||
" Please shorten"
|
||||
"might not function correctly. Please shorten"
|
||||
% name)
|
||||
self.__name = name
|
||||
|
||||
|
@ -124,11 +123,17 @@ class Jail:
|
|||
def getDatabase(self):
|
||||
return self.__db
|
||||
|
||||
def getFilter(self):
|
||||
@property
|
||||
def filter(self):
|
||||
"""The filter which the jail is using to monitor log files.
|
||||
"""
|
||||
return self.__filter
|
||||
|
||||
def getAction(self):
|
||||
return self.__action
|
||||
@property
|
||||
def actions(self):
|
||||
"""Actions object used to manage actions for jail.
|
||||
"""
|
||||
return self.__actions
|
||||
|
||||
def putFailTicket(self, ticket):
|
||||
self.__queue.put(ticket)
|
||||
|
@ -143,36 +148,36 @@ class Jail:
|
|||
|
||||
def start(self):
|
||||
self.__filter.start()
|
||||
self.__action.start()
|
||||
self.__actions.start()
|
||||
# Restore any previous valid bans from the database
|
||||
if self.__db is not None:
|
||||
for ticket in self.__db.getBans(
|
||||
jail=self, bantime=self.__action.getBanTime()):
|
||||
jail=self, bantime=self.__actions.getBanTime()):
|
||||
self.__queue.put(ticket)
|
||||
logSys.info("Jail '%s' started" % self.__name)
|
||||
|
||||
def stop(self):
|
||||
self.__filter.stop()
|
||||
self.__action.stop()
|
||||
self.__actions.stop()
|
||||
self.__filter.join()
|
||||
self.__action.join()
|
||||
self.__actions.join()
|
||||
logSys.info("Jail '%s' stopped" % self.__name)
|
||||
|
||||
def isAlive(self):
|
||||
isAlive0 = self.__filter.isAlive()
|
||||
isAlive1 = self.__action.isAlive()
|
||||
isAlive1 = self.__actions.isAlive()
|
||||
return isAlive0 or isAlive1
|
||||
|
||||
def setIdle(self, value):
|
||||
self.__filter.setIdle(value)
|
||||
self.__action.setIdle(value)
|
||||
self.__actions.setIdle(value)
|
||||
|
||||
def getIdle(self):
|
||||
return self.__filter.getIdle() or self.__action.getIdle()
|
||||
return self.__filter.getIdle() or self.__actions.getIdle()
|
||||
|
||||
def getStatus(self):
|
||||
fStatus = self.__filter.status()
|
||||
aStatus = self.__action.status()
|
||||
aStatus = self.__actions.status()
|
||||
ret = [("filter", fStatus),
|
||||
("action", aStatus)]
|
||||
return ret
|
||||
|
|
|
@ -21,137 +21,84 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko"
|
|||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2013- Yaroslav Halchenko"
|
||||
__license__ = "GPL"
|
||||
|
||||
from fail2ban.exceptions import DuplicateJailException, UnknownJailException
|
||||
|
||||
from jail import Jail
|
||||
from threading import Lock
|
||||
from collections import Mapping
|
||||
|
||||
##
|
||||
# Handles the jails.
|
||||
#
|
||||
# This class handles the jails. Creation, deletion or access to a jail must be
|
||||
# done through this class. This class is thread-safe which is not the case of
|
||||
# the jail itself, including filter and actions.
|
||||
from ..exceptions import DuplicateJailException, UnknownJailException
|
||||
from .jail import Jail
|
||||
|
||||
class Jails:
|
||||
|
||||
##
|
||||
# Constructor.
|
||||
class Jails(Mapping):
|
||||
"""Handles the jails.
|
||||
|
||||
This class handles the jails. Creation, deletion or access to a jail
|
||||
must be done through this class. This class is thread-safe which is
|
||||
not the case of the jail itself, including filter and actions. This
|
||||
class is based on Mapping type, and the `add` method must be used to
|
||||
add additional jails.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialise an empty Jails instance.
|
||||
"""
|
||||
self.__lock = Lock()
|
||||
self.__jails = dict()
|
||||
|
||||
##
|
||||
# Adds a jail.
|
||||
#
|
||||
# Adds a new jail which should use the given backend. Raises a
|
||||
# <code>DuplicateJailException</code> if the jail is already defined.
|
||||
# @param name The name of the jail
|
||||
# @param backend The backend to use
|
||||
self._jails = dict()
|
||||
|
||||
def add(self, name, backend, db=None):
|
||||
"""Adds a jail.
|
||||
|
||||
Adds a new jail if not already present which should use the
|
||||
given backend.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
The name of the jail.
|
||||
backend : str
|
||||
The backend to use.
|
||||
|
||||
Raises
|
||||
------
|
||||
DuplicateJailException
|
||||
If jail name is already present.
|
||||
"""
|
||||
try:
|
||||
self.__lock.acquire()
|
||||
if self.__jails.has_key(name):
|
||||
if name in self._jails:
|
||||
raise DuplicateJailException(name)
|
||||
else:
|
||||
self.__jails[name] = Jail(name, backend, db)
|
||||
self._jails[name] = Jail(name, backend, db)
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
||||
##
|
||||
# Removes a jail.
|
||||
#
|
||||
# Removes the jail <code>name</code>. Raise an <code>UnknownJailException</code>
|
||||
# if the jail does not exist.
|
||||
# @param name The name of the jail
|
||||
|
||||
def remove(self, name):
|
||||
def __getitem__(self, name):
|
||||
try:
|
||||
self.__lock.acquire()
|
||||
if self.__jails.has_key(name):
|
||||
del self.__jails[name]
|
||||
else:
|
||||
return self._jails[name]
|
||||
except KeyError:
|
||||
raise UnknownJailException(name)
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
||||
##
|
||||
# Returns a jail.
|
||||
#
|
||||
# Returns the jail <code>name</code>. Raise an <code>UnknownJailException</code>
|
||||
# if the jail does not exist.
|
||||
# @param name The name of the jail
|
||||
|
||||
def get(self, name):
|
||||
def __delitem__(self, name):
|
||||
try:
|
||||
self.__lock.acquire()
|
||||
if self.__jails.has_key(name):
|
||||
jail = self.__jails[name]
|
||||
return jail
|
||||
else:
|
||||
del self._jails[name]
|
||||
except KeyError:
|
||||
raise UnknownJailException(name)
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
||||
##
|
||||
# Returns an action class instance.
|
||||
#
|
||||
# Returns the action object of the jail <code>name</code>. Raise an
|
||||
# <code>UnknownJailException</code> if the jail does not exist.
|
||||
# @param name The name of the jail
|
||||
|
||||
def getAction(self, name):
|
||||
def __len__(self):
|
||||
try:
|
||||
self.__lock.acquire()
|
||||
if self.__jails.has_key(name):
|
||||
action = self.__jails[name].getAction()
|
||||
return action
|
||||
else:
|
||||
raise UnknownJailException(name)
|
||||
return len(self._jails)
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
||||
##
|
||||
# Returns a filter class instance.
|
||||
#
|
||||
# Returns the filter object of the jail <code>name</code>. Raise an
|
||||
# <code>UnknownJailException</code> if the jail does not exist.
|
||||
# @param name The name of the jail
|
||||
|
||||
def getFilter(self, name):
|
||||
def __iter__(self):
|
||||
try:
|
||||
self.__lock.acquire()
|
||||
if self.__jails.has_key(name):
|
||||
action = self.__jails[name].getFilter()
|
||||
return action
|
||||
else:
|
||||
raise UnknownJailException(name)
|
||||
return iter(self._jails)
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
||||
##
|
||||
# Returns the jails.
|
||||
#
|
||||
# Returns a copy of the jails list.
|
||||
|
||||
def getAll(self):
|
||||
try:
|
||||
self.__lock.acquire()
|
||||
return self.__jails.copy()
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
||||
##
|
||||
# Returns the size of the jails.
|
||||
#
|
||||
# Returns the number of jails.
|
||||
|
||||
def size(self):
|
||||
try:
|
||||
self.__lock.acquire()
|
||||
return len(self.__jails)
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
||||
|
|
|
@ -25,15 +25,16 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
|||
__license__ = "GPL"
|
||||
|
||||
from threading import Lock, RLock
|
||||
from jails import Jails
|
||||
from filter import FileFilter, JournalFilter
|
||||
from transmitter import Transmitter
|
||||
from asyncserver import AsyncServer
|
||||
from asyncserver import AsyncServerException
|
||||
from database import Fail2BanDb
|
||||
from fail2ban import version
|
||||
import logging, logging.handlers, sys, os, signal
|
||||
|
||||
from .jails import Jails
|
||||
from .filter import FileFilter, JournalFilter
|
||||
from .transmitter import Transmitter
|
||||
from .asyncserver import AsyncServer, AsyncServerException
|
||||
from .database import Fail2BanDb
|
||||
from .action import CommandAction
|
||||
from .. import version
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
||||
|
@ -121,10 +122,10 @@ class Server:
|
|||
def addJail(self, name, backend):
|
||||
self.__jails.add(name, backend, self.__db)
|
||||
if self.__db is not None:
|
||||
self.__db.addJail(self.__jails.get(name))
|
||||
self.__db.addJail(self.__jails[name])
|
||||
|
||||
def delJail(self, name):
|
||||
self.__jails.remove(name)
|
||||
del self.__jails[name]
|
||||
if self.__db is not None:
|
||||
self.__db.delJailName(name)
|
||||
|
||||
|
@ -132,7 +133,7 @@ class Server:
|
|||
try:
|
||||
self.__lock.acquire()
|
||||
if not self.isAlive(name):
|
||||
self.__jails.get(name).start()
|
||||
self.__jails[name].start()
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
||||
|
@ -141,7 +142,7 @@ class Server:
|
|||
try:
|
||||
self.__lock.acquire()
|
||||
if self.isAlive(name):
|
||||
self.__jails.get(name).stop()
|
||||
self.__jails[name].stop()
|
||||
self.delJail(name)
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
@ -150,43 +151,43 @@ class Server:
|
|||
logSys.info("Stopping all jails")
|
||||
try:
|
||||
self.__lock.acquire()
|
||||
for jail in self.__jails.getAll():
|
||||
for jail in self.__jails.keys():
|
||||
self.stopJail(jail)
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
||||
def isAlive(self, name):
|
||||
return self.__jails.get(name).isAlive()
|
||||
return self.__jails[name].isAlive()
|
||||
|
||||
def setIdleJail(self, name, value):
|
||||
self.__jails.get(name).setIdle(value)
|
||||
self.__jails[name].setIdle(value)
|
||||
return True
|
||||
|
||||
def getIdleJail(self, name):
|
||||
return self.__jails.get(name).getIdle()
|
||||
return self.__jails[name].getIdle()
|
||||
|
||||
# Filter
|
||||
def addIgnoreIP(self, name, ip):
|
||||
self.__jails.getFilter(name).addIgnoreIP(ip)
|
||||
self.__jails[name].filter.addIgnoreIP(ip)
|
||||
|
||||
def delIgnoreIP(self, name, ip):
|
||||
self.__jails.getFilter(name).delIgnoreIP(ip)
|
||||
self.__jails[name].filter.delIgnoreIP(ip)
|
||||
|
||||
def getIgnoreIP(self, name):
|
||||
return self.__jails.getFilter(name).getIgnoreIP()
|
||||
return self.__jails[name].filter.getIgnoreIP()
|
||||
|
||||
def addLogPath(self, name, fileName, tail=False):
|
||||
filter_ = self.__jails.getFilter(name)
|
||||
filter_ = self.__jails[name].filter
|
||||
if isinstance(filter_, FileFilter):
|
||||
filter_.addLogPath(fileName, tail)
|
||||
|
||||
def delLogPath(self, name, fileName):
|
||||
filter_ = self.__jails.getFilter(name)
|
||||
filter_ = self.__jails[name].filter
|
||||
if isinstance(filter_, FileFilter):
|
||||
filter_.delLogPath(fileName)
|
||||
|
||||
def getLogPath(self, name):
|
||||
filter_ = self.__jails.getFilter(name)
|
||||
filter_ = self.__jails[name].filter
|
||||
if isinstance(filter_, FileFilter):
|
||||
return [m.getFileName()
|
||||
for m in filter_.getLogPath()]
|
||||
|
@ -195,17 +196,17 @@ class Server:
|
|||
return []
|
||||
|
||||
def addJournalMatch(self, name, match): # pragma: systemd no cover
|
||||
filter_ = self.__jails.getFilter(name)
|
||||
filter_ = self.__jails[name].filter
|
||||
if isinstance(filter_, JournalFilter):
|
||||
filter_.addJournalMatch(match)
|
||||
|
||||
def delJournalMatch(self, name, match): # pragma: systemd no cover
|
||||
filter_ = self.__jails.getFilter(name)
|
||||
filter_ = self.__jails[name].filter
|
||||
if isinstance(filter_, JournalFilter):
|
||||
filter_.delJournalMatch(match)
|
||||
|
||||
def getJournalMatch(self, name): # pragma: systemd no cover
|
||||
filter_ = self.__jails.getFilter(name)
|
||||
filter_ = self.__jails[name].filter
|
||||
if isinstance(filter_, JournalFilter):
|
||||
return filter_.getJournalMatch()
|
||||
else:
|
||||
|
@ -213,154 +214,109 @@ class Server:
|
|||
return []
|
||||
|
||||
def setLogEncoding(self, name, encoding):
|
||||
filter_ = self.__jails.getFilter(name)
|
||||
filter_ = self.__jails[name].filter
|
||||
if isinstance(filter_, FileFilter):
|
||||
filter_.setLogEncoding(encoding)
|
||||
|
||||
def getLogEncoding(self, name):
|
||||
filter_ = self.__jails.getFilter(name)
|
||||
filter_ = self.__jails[name].filter
|
||||
if isinstance(filter_, FileFilter):
|
||||
return filter_.getLogEncoding()
|
||||
|
||||
def setFindTime(self, name, value):
|
||||
self.__jails.getFilter(name).setFindTime(value)
|
||||
self.__jails[name].filter.setFindTime(value)
|
||||
|
||||
def getFindTime(self, name):
|
||||
return self.__jails.getFilter(name).getFindTime()
|
||||
return self.__jails[name].filter.getFindTime()
|
||||
|
||||
def setDatePattern(self, name, pattern):
|
||||
self.__jails.getFilter(name).setDatePattern(pattern)
|
||||
self.__jails[name].filter.setDatePattern(pattern)
|
||||
|
||||
def getDatePattern(self, name):
|
||||
return self.__jails.getFilter(name).getDatePattern()
|
||||
return self.__jails[name].filter.getDatePattern()
|
||||
|
||||
def setIgnoreCommand(self, name, value):
|
||||
self.__jails.getFilter(name).setIgnoreCommand(value)
|
||||
self.__jails[name].filter.setIgnoreCommand(value)
|
||||
|
||||
def getIgnoreCommand(self, name):
|
||||
return self.__jails.getFilter(name).getIgnoreCommand()
|
||||
return self.__jails[name].filter.getIgnoreCommand()
|
||||
|
||||
def addFailRegex(self, name, value):
|
||||
self.__jails.getFilter(name).addFailRegex(value)
|
||||
self.__jails[name].filter.addFailRegex(value)
|
||||
|
||||
def delFailRegex(self, name, index):
|
||||
self.__jails.getFilter(name).delFailRegex(index)
|
||||
self.__jails[name].filter.delFailRegex(index)
|
||||
|
||||
def getFailRegex(self, name):
|
||||
return self.__jails.getFilter(name).getFailRegex()
|
||||
return self.__jails[name].filter.getFailRegex()
|
||||
|
||||
def addIgnoreRegex(self, name, value):
|
||||
self.__jails.getFilter(name).addIgnoreRegex(value)
|
||||
self.__jails[name].filter.addIgnoreRegex(value)
|
||||
|
||||
def delIgnoreRegex(self, name, index):
|
||||
self.__jails.getFilter(name).delIgnoreRegex(index)
|
||||
self.__jails[name].filter.delIgnoreRegex(index)
|
||||
|
||||
def getIgnoreRegex(self, name):
|
||||
return self.__jails.getFilter(name).getIgnoreRegex()
|
||||
return self.__jails[name].filter.getIgnoreRegex()
|
||||
|
||||
def setUseDns(self, name, value):
|
||||
self.__jails.getFilter(name).setUseDns(value)
|
||||
self.__jails[name].filter.setUseDns(value)
|
||||
|
||||
def getUseDns(self, name):
|
||||
return self.__jails.getFilter(name).getUseDns()
|
||||
return self.__jails[name].filter.getUseDns()
|
||||
|
||||
def setMaxRetry(self, name, value):
|
||||
self.__jails.getFilter(name).setMaxRetry(value)
|
||||
self.__jails[name].filter.setMaxRetry(value)
|
||||
|
||||
def getMaxRetry(self, name):
|
||||
return self.__jails.getFilter(name).getMaxRetry()
|
||||
return self.__jails[name].filter.getMaxRetry()
|
||||
|
||||
def setMaxLines(self, name, value):
|
||||
self.__jails.getFilter(name).setMaxLines(value)
|
||||
self.__jails[name].filter.setMaxLines(value)
|
||||
|
||||
def getMaxLines(self, name):
|
||||
return self.__jails.getFilter(name).getMaxLines()
|
||||
return self.__jails[name].filter.getMaxLines()
|
||||
|
||||
# Action
|
||||
def addAction(self, name, value):
|
||||
self.__jails.getAction(name).addAction(value)
|
||||
|
||||
def getLastAction(self, name):
|
||||
return self.__jails.getAction(name).getLastAction()
|
||||
def addAction(self, name, value, *args):
|
||||
self.__jails[name].actions.add(value, *args)
|
||||
|
||||
def getActions(self, name):
|
||||
return self.__jails.getAction(name).getActions()
|
||||
return self.__jails[name].actions
|
||||
|
||||
def delAction(self, name, value):
|
||||
self.__jails.getAction(name).delAction(value)
|
||||
del self.__jails[name].actions[value]
|
||||
|
||||
def setCInfo(self, name, action, key, value):
|
||||
self.__jails.getAction(name).getAction(action).setCInfo(key, value)
|
||||
|
||||
def getCInfo(self, name, action, key):
|
||||
return self.__jails.getAction(name).getAction(action).getCInfo(key)
|
||||
|
||||
def delCInfo(self, name, action, key):
|
||||
self.__jails.getAction(name).getAction(action).delCInfo(key)
|
||||
def getAction(self, name, value):
|
||||
return self.__jails[name].actions[value]
|
||||
|
||||
def setBanTime(self, name, value):
|
||||
self.__jails.getAction(name).setBanTime(value)
|
||||
self.__jails[name].actions.setBanTime(value)
|
||||
|
||||
def setBanIP(self, name, value):
|
||||
return self.__jails.getFilter(name).addBannedIP(value)
|
||||
return self.__jails[name].filter.addBannedIP(value)
|
||||
|
||||
def setUnbanIP(self, name, value):
|
||||
return self.__jails.getAction(name).removeBannedIP(value)
|
||||
self.__jails[name].actions.removeBannedIP(value)
|
||||
|
||||
def getBanTime(self, name):
|
||||
return self.__jails.getAction(name).getBanTime()
|
||||
|
||||
def setActionStart(self, name, action, value):
|
||||
self.__jails.getAction(name).getAction(action).setActionStart(value)
|
||||
|
||||
def getActionStart(self, name, action):
|
||||
return self.__jails.getAction(name).getAction(action).getActionStart()
|
||||
|
||||
def setActionStop(self, name, action, value):
|
||||
self.__jails.getAction(name).getAction(action).setActionStop(value)
|
||||
|
||||
def getActionStop(self, name, action):
|
||||
return self.__jails.getAction(name).getAction(action).getActionStop()
|
||||
|
||||
def setActionCheck(self, name, action, value):
|
||||
self.__jails.getAction(name).getAction(action).setActionCheck(value)
|
||||
|
||||
def getActionCheck(self, name, action):
|
||||
return self.__jails.getAction(name).getAction(action).getActionCheck()
|
||||
|
||||
def setActionBan(self, name, action, value):
|
||||
self.__jails.getAction(name).getAction(action).setActionBan(value)
|
||||
|
||||
def getActionBan(self, name, action):
|
||||
return self.__jails.getAction(name).getAction(action).getActionBan()
|
||||
|
||||
def setActionUnban(self, name, action, value):
|
||||
self.__jails.getAction(name).getAction(action).setActionUnban(value)
|
||||
|
||||
def getActionUnban(self, name, action):
|
||||
return self.__jails.getAction(name).getAction(action).getActionUnban()
|
||||
|
||||
def setActionTimeout(self, name, action, value):
|
||||
self.__jails.getAction(name).getAction(action).setTimeout(value)
|
||||
|
||||
def getActionTimeout(self, name, action):
|
||||
return self.__jails.getAction(name).getAction(action).getTimeout()
|
||||
return self.__jails[name].actions.getBanTime()
|
||||
|
||||
# Status
|
||||
def status(self):
|
||||
try:
|
||||
self.__lock.acquire()
|
||||
jails = list(self.__jails.getAll())
|
||||
jails = list(self.__jails)
|
||||
jails.sort()
|
||||
jailList = ", ".join(jails)
|
||||
ret = [("Number of jail", self.__jails.size()),
|
||||
ret = [("Number of jail", len(self.__jails)),
|
||||
("Jail list", jailList)]
|
||||
return ret
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
||||
def statusJail(self, name):
|
||||
return self.__jails.get(name).getStatus()
|
||||
return self.__jails[name].getStatus()
|
||||
|
||||
# Logging
|
||||
|
||||
|
@ -488,7 +444,7 @@ class Server:
|
|||
return "flushed"
|
||||
|
||||
def setDatabase(self, filename):
|
||||
if self.__jails.size() == 0:
|
||||
if len(self.__jails) == 0:
|
||||
if filename.lower() == "none":
|
||||
self.__db = None
|
||||
else:
|
||||
|
|
|
@ -25,6 +25,7 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
|||
__license__ = "GPL"
|
||||
|
||||
import logging, time
|
||||
import json
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
@ -226,56 +227,29 @@ class Transmitter:
|
|||
return self.__server.setBanIP(name,value)
|
||||
elif command[1] == "unbanip":
|
||||
value = command[2]
|
||||
return self.__server.setUnbanIP(name,value)
|
||||
self.__server.setUnbanIP(name, value)
|
||||
return value
|
||||
elif command[1] == "addaction":
|
||||
value = command[2]
|
||||
self.__server.addAction(name, value)
|
||||
return self.__server.getLastAction(name).getName()
|
||||
args = [command[2]]
|
||||
if len(command) > 3:
|
||||
args.extend([command[3], json.loads(command[4])])
|
||||
self.__server.addAction(name, *args)
|
||||
return args[0]
|
||||
elif command[1] == "delaction":
|
||||
value = command[2]
|
||||
self.__server.delAction(name, value)
|
||||
return None
|
||||
elif command[1] == "setcinfo":
|
||||
act = command[2]
|
||||
key = command[3]
|
||||
value = " ".join(command[4:])
|
||||
self.__server.setCInfo(name, act, key, value)
|
||||
return self.__server.getCInfo(name, act, key)
|
||||
elif command[1] == "delcinfo":
|
||||
act = command[2]
|
||||
key = command[3]
|
||||
self.__server.delCInfo(name, act, key)
|
||||
return None
|
||||
elif command[1] == "actionstart":
|
||||
act = command[2]
|
||||
value = " ".join(command[3:])
|
||||
self.__server.setActionStart(name, act, value)
|
||||
return self.__server.getActionStart(name, act)
|
||||
elif command[1] == "actionstop":
|
||||
act = command[2]
|
||||
value = " ".join(command[3:])
|
||||
self.__server.setActionStop(name, act, value)
|
||||
return self.__server.getActionStop(name, act)
|
||||
elif command[1] == "actioncheck":
|
||||
act = command[2]
|
||||
value = " ".join(command[3:])
|
||||
self.__server.setActionCheck(name, act, value)
|
||||
return self.__server.getActionCheck(name, act)
|
||||
elif command[1] == "actionban":
|
||||
act = command[2]
|
||||
value = " ".join(command[3:])
|
||||
self.__server.setActionBan(name, act, value)
|
||||
return self.__server.getActionBan(name, act)
|
||||
elif command[1] == "actionunban":
|
||||
act = command[2]
|
||||
value = " ".join(command[3:])
|
||||
self.__server.setActionUnban(name, act, value)
|
||||
return self.__server.getActionUnban(name, act)
|
||||
elif command[1] == "timeout":
|
||||
act = command[2]
|
||||
value = int(command[3])
|
||||
self.__server.setActionTimeout(name, act, value)
|
||||
return self.__server.getActionTimeout(name, act)
|
||||
elif command[1] == "action":
|
||||
actionname = command[2]
|
||||
actionkey = command[3]
|
||||
action = self.__server.getAction(name, actionname)
|
||||
if callable(getattr(action, actionkey, None)):
|
||||
actionvalue = json.loads(command[4]) if len(command)>4 else {}
|
||||
return getattr(action, actionkey)(**actionvalue)
|
||||
else:
|
||||
actionvalue = command[4]
|
||||
setattr(action, actionkey, actionvalue)
|
||||
return getattr(action, actionkey)
|
||||
raise Exception("Invalid command (no set action or not yet implemented)")
|
||||
|
||||
def __commandGet(self, command):
|
||||
|
@ -327,31 +301,25 @@ class Transmitter:
|
|||
elif command[1] == "bantime":
|
||||
return self.__server.getBanTime(name)
|
||||
elif command[1] == "actions":
|
||||
return self.__server.getActions(name)
|
||||
elif command[1] == "addaction":
|
||||
return self.__server.getLastAction(name).getName()
|
||||
elif command[1] == "actionstart":
|
||||
act = command[2]
|
||||
return self.__server.getActionStart(name, act)
|
||||
elif command[1] == "actionstop":
|
||||
act = command[2]
|
||||
return self.__server.getActionStop(name, act)
|
||||
elif command[1] == "actioncheck":
|
||||
act = command[2]
|
||||
return self.__server.getActionCheck(name, act)
|
||||
elif command[1] == "actionban":
|
||||
act = command[2]
|
||||
return self.__server.getActionBan(name, act)
|
||||
elif command[1] == "actionunban":
|
||||
act = command[2]
|
||||
return self.__server.getActionUnban(name, act)
|
||||
elif command[1] == "cinfo":
|
||||
act = command[2]
|
||||
key = command[3]
|
||||
return self.__server.getCInfo(name, act, key)
|
||||
elif command[1] == "timeout":
|
||||
act = command[2]
|
||||
return self.__server.getActionTimeout(name, act)
|
||||
return self.__server.getActions(name).keys()
|
||||
elif command[1] == "action":
|
||||
actionname = command[2]
|
||||
actionvalue = command[3]
|
||||
action = self.__server.getAction(name, actionname)
|
||||
return getattr(action, actionvalue)
|
||||
elif command[1] == "actionproperties":
|
||||
actionname = command[2]
|
||||
action = self.__server.getAction(name, actionname)
|
||||
return [
|
||||
key for key in dir(action)
|
||||
if not key.startswith("_") and
|
||||
not callable(getattr(action, key))]
|
||||
elif command[1] == "actionmethods":
|
||||
actionname = command[2]
|
||||
action = self.__server.getAction(name, actionname)
|
||||
return [
|
||||
key for key in dir(action)
|
||||
if not key.startswith("_") and callable(getattr(action, key))]
|
||||
raise Exception("Invalid command (no get action or not yet implemented)")
|
||||
|
||||
def status(self, command):
|
||||
|
|
|
@ -26,39 +26,49 @@ __license__ = "GPL"
|
|||
|
||||
import unittest, time
|
||||
import sys, os, tempfile
|
||||
from fail2ban.server.actions import Actions
|
||||
from dummyjail import DummyJail
|
||||
|
||||
class ExecuteActions(unittest.TestCase):
|
||||
from ..server.actions import Actions
|
||||
from .dummyjail import DummyJail
|
||||
from .utils import LogCaptureTestCase
|
||||
|
||||
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
||||
|
||||
class ExecuteActions(LogCaptureTestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""Call before every test case."""
|
||||
super(ExecuteActions, self).setUp()
|
||||
self.__jail = DummyJail()
|
||||
self.__actions = Actions(self.__jail)
|
||||
self.__tmpfile, self.__tmpfilename = tempfile.mkstemp()
|
||||
|
||||
def tearDown(self):
|
||||
super(ExecuteActions, self).tearDown()
|
||||
os.remove(self.__tmpfilename)
|
||||
|
||||
def defaultActions(self):
|
||||
self.__actions.addAction('ip')
|
||||
self.__ip = self.__actions.getAction('ip')
|
||||
self.__ip.setActionStart('echo ip start 64 >> "%s"' % self.__tmpfilename )
|
||||
self.__ip.setActionBan('echo ip ban <ip> >> "%s"' % self.__tmpfilename )
|
||||
self.__ip.setActionUnban('echo ip unban <ip> >> "%s"' % self.__tmpfilename )
|
||||
self.__ip.setActionCheck('echo ip check <ip> >> "%s"' % self.__tmpfilename )
|
||||
self.__ip.setActionStop('echo ip stop >> "%s"' % self.__tmpfilename )
|
||||
self.__actions.add('ip')
|
||||
self.__ip = self.__actions['ip']
|
||||
self.__ip.actionstart = 'echo ip start 64 >> "%s"' % self.__tmpfilename
|
||||
self.__ip.actionban = 'echo ip ban <ip> >> "%s"' % self.__tmpfilename
|
||||
self.__ip.actionunban = 'echo ip unban <ip> >> "%s"' % self.__tmpfilename
|
||||
self.__ip.actioncheck = 'echo ip check <ip> >> "%s"' % self.__tmpfilename
|
||||
self.__ip.actionstop = 'echo ip stop >> "%s"' % self.__tmpfilename
|
||||
|
||||
def testActionsAddDuplicateName(self):
|
||||
self.__actions.add('test')
|
||||
self.assertRaises(ValueError, self.__actions.add, 'test')
|
||||
|
||||
def testActionsManipulation(self):
|
||||
self.__actions.addAction('test')
|
||||
self.assertTrue(self.__actions.getAction('test'))
|
||||
self.assertTrue(self.__actions.getLastAction())
|
||||
self.assertRaises(KeyError,self.__actions.getAction,*['nonexistant action'])
|
||||
self.__actions.addAction('test1')
|
||||
self.__actions.delAction('test')
|
||||
self.__actions.delAction('test1')
|
||||
self.assertRaises(KeyError, self.__actions.getAction, *['test'])
|
||||
self.assertRaises(IndexError,self.__actions.getLastAction)
|
||||
self.__actions.add('test')
|
||||
self.assertTrue(self.__actions['test'])
|
||||
self.assertTrue('test' in self.__actions)
|
||||
self.assertFalse('nonexistant action' in self.__actions)
|
||||
self.__actions.add('test1')
|
||||
del self.__actions['test']
|
||||
del self.__actions['test1']
|
||||
self.assertFalse('test' in self.__actions)
|
||||
self.assertEqual(len(self.__actions), 0)
|
||||
|
||||
self.__actions.setBanTime(127)
|
||||
self.assertEqual(self.__actions.getBanTime(),127)
|
||||
|
@ -77,3 +87,55 @@ class ExecuteActions(unittest.TestCase):
|
|||
self.assertEqual(self.__actions.status(),[("Currently banned", 0 ),
|
||||
("Total banned", 0 ), ("IP list", [] )])
|
||||
|
||||
|
||||
def testAddActionPython(self):
|
||||
self.__actions.add(
|
||||
"Action", os.path.join(TEST_FILES_DIR, "action.d/action.py"),
|
||||
{'opt1': 'value'})
|
||||
|
||||
self.assertTrue(self._is_logged("TestAction initialised"))
|
||||
|
||||
self.__actions.start()
|
||||
time.sleep(3)
|
||||
self.assertTrue(self._is_logged("TestAction action start"))
|
||||
|
||||
self.__actions.stop()
|
||||
self.__actions.join()
|
||||
self.assertTrue(self._is_logged("TestAction action stop"))
|
||||
|
||||
self.assertRaises(IOError,
|
||||
self.__actions.add, "Action3", "/does/not/exist.py", {})
|
||||
|
||||
# With optional argument
|
||||
self.__actions.add(
|
||||
"Action4", os.path.join(TEST_FILES_DIR, "action.d/action.py"),
|
||||
{'opt1': 'value', 'opt2': 'value2'})
|
||||
# With too many arguments
|
||||
self.assertRaises(
|
||||
TypeError, self.__actions.add, "Action5",
|
||||
os.path.join(TEST_FILES_DIR, "action.d/action.py"),
|
||||
{'opt1': 'value', 'opt2': 'value2', 'opt3': 'value3'})
|
||||
# Missing required argument
|
||||
self.assertRaises(
|
||||
TypeError, self.__actions.add, "Action5",
|
||||
os.path.join(TEST_FILES_DIR, "action.d/action.py"), {})
|
||||
|
||||
def testAddPythonActionNOK(self):
|
||||
self.assertRaises(RuntimeError, self.__actions.add,
|
||||
"Action", os.path.join(TEST_FILES_DIR,
|
||||
"action.d/action_noAction.py"),
|
||||
{})
|
||||
self.assertRaises(RuntimeError, self.__actions.add,
|
||||
"Action", os.path.join(TEST_FILES_DIR,
|
||||
"action.d/action_nomethod.py"),
|
||||
{})
|
||||
self.__actions.add(
|
||||
"Action", os.path.join(TEST_FILES_DIR,
|
||||
"action.d/action_errors.py"),
|
||||
{})
|
||||
self.__actions.start()
|
||||
time.sleep(3)
|
||||
self.assertTrue(self._is_logged("Failed to start"))
|
||||
self.__actions.stop()
|
||||
self.__actions.join()
|
||||
self.assertTrue(self._is_logged("Failed to stop"))
|
||||
|
|
|
@ -27,26 +27,21 @@ __license__ = "GPL"
|
|||
import time
|
||||
import logging, sys
|
||||
|
||||
from fail2ban.server.action import Action
|
||||
from ..server.action import CommandAction, CallingMap
|
||||
|
||||
from fail2ban.tests.utils import LogCaptureTestCase
|
||||
from .utils import LogCaptureTestCase
|
||||
|
||||
class ExecuteAction(LogCaptureTestCase):
|
||||
class CommandActionTest(LogCaptureTestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""Call before every test case."""
|
||||
self.__action = Action("Test")
|
||||
self.__action = CommandAction(None, "Test")
|
||||
LogCaptureTestCase.setUp(self)
|
||||
|
||||
def tearDown(self):
|
||||
"""Call after every test case."""
|
||||
LogCaptureTestCase.tearDown(self)
|
||||
self.__action.execActionStop()
|
||||
|
||||
def testNameChange(self):
|
||||
self.assertEqual(self.__action.getName(), "Test")
|
||||
self.__action.setName("Tricky Test")
|
||||
self.assertEqual(self.__action.getName(), "Tricky Test")
|
||||
self.__action.stop()
|
||||
|
||||
def testSubstituteRecursiveTags(self):
|
||||
aInfo = {
|
||||
|
@ -55,18 +50,18 @@ class ExecuteAction(LogCaptureTestCase):
|
|||
'xyz': "890 <ABC>",
|
||||
}
|
||||
# Recursion is bad
|
||||
self.assertFalse(Action.substituteRecursiveTags({'A': '<A>'}))
|
||||
self.assertFalse(Action.substituteRecursiveTags({'A': '<B>', 'B': '<A>'}))
|
||||
self.assertFalse(Action.substituteRecursiveTags({'A': '<B>', 'B': '<C>', 'C': '<A>'}))
|
||||
# part recursion
|
||||
self.assertFalse(Action.substituteRecursiveTags({'A': 'to=<B> fromip=<IP>', 'C': '<B>', 'B': '<C>', 'D': ''}))
|
||||
self.assertFalse(Action.substituteRecursiveTags({'failregex': 'to=<honeypot> fromip=<IP>', 'sweet': '<honeypot>', 'honeypot': '<sweet>', 'ignoreregex': ''}))
|
||||
self.assertFalse(CommandAction.substituteRecursiveTags({'A': '<A>'}))
|
||||
self.assertFalse(CommandAction.substituteRecursiveTags({'A': '<B>', 'B': '<A>'}))
|
||||
self.assertFalse(CommandAction.substituteRecursiveTags({'A': '<B>', 'B': '<C>', 'C': '<A>'}))
|
||||
# Unresolveable substition
|
||||
self.assertFalse(CommandAction.substituteRecursiveTags({'A': 'to=<B> fromip=<IP>', 'C': '<B>', 'B': '<C>', 'D': ''}))
|
||||
self.assertFalse(CommandAction.substituteRecursiveTags({'failregex': 'to=<honeypot> fromip=<IP>', 'sweet': '<honeypot>', 'honeypot': '<sweet>', 'ignoreregex': ''}))
|
||||
# missing tags are ok
|
||||
self.assertEqual(Action.substituteRecursiveTags({'A': '<C>'}), {'A': '<C>'})
|
||||
self.assertEqual(Action.substituteRecursiveTags({'A': '<C> <D> <X>','X':'fun'}), {'A': '<C> <D> fun', 'X':'fun'})
|
||||
self.assertEqual(Action.substituteRecursiveTags({'A': '<C> <B>', 'B': 'cool'}), {'A': '<C> cool', 'B': 'cool'})
|
||||
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C>'}), {'A': '<C>'})
|
||||
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C> <D> <X>','X':'fun'}), {'A': '<C> <D> fun', 'X':'fun'})
|
||||
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C> <B>', 'B': 'cool'}), {'A': '<C> cool', 'B': 'cool'})
|
||||
# rest is just cool
|
||||
self.assertEqual(Action.substituteRecursiveTags(aInfo),
|
||||
self.assertEqual(CommandAction.substituteRecursiveTags(aInfo),
|
||||
{ 'HOST': "192.0.2.0",
|
||||
'ABC': '123 192.0.2.0',
|
||||
'xyz': '890 123 192.0.2.0',
|
||||
|
@ -102,90 +97,102 @@ class ExecuteAction(LogCaptureTestCase):
|
|||
|
||||
# Callable
|
||||
self.assertEqual(
|
||||
self.__action.replaceTag("09 <callable> 11",
|
||||
{'callable': lambda: str(10)}),
|
||||
self.__action.replaceTag("09 <callme> 11",
|
||||
CallingMap(callme=lambda: str(10))),
|
||||
"09 10 11")
|
||||
|
||||
# As tag not present, therefore callable should not be called
|
||||
# Will raise ValueError if it is
|
||||
self.assertEqual(
|
||||
self.__action.replaceTag("abc",
|
||||
{'callable': lambda: int("a")}), "abc")
|
||||
CallingMap(callme=lambda: int("a"))), "abc")
|
||||
|
||||
def testExecuteActionBan(self):
|
||||
self.__action.setActionStart("touch /tmp/fail2ban.test")
|
||||
self.assertEqual(self.__action.getActionStart(), "touch /tmp/fail2ban.test")
|
||||
self.__action.setActionStop("rm -f /tmp/fail2ban.test")
|
||||
self.assertEqual(self.__action.getActionStop(), 'rm -f /tmp/fail2ban.test')
|
||||
self.__action.setActionBan("echo -n")
|
||||
self.assertEqual(self.__action.getActionBan(), 'echo -n')
|
||||
self.__action.setActionCheck("[ -e /tmp/fail2ban.test ]")
|
||||
self.assertEqual(self.__action.getActionCheck(), '[ -e /tmp/fail2ban.test ]')
|
||||
self.__action.setActionUnban("true")
|
||||
self.assertEqual(self.__action.getActionUnban(), 'true')
|
||||
self.__action.actionstart = "touch /tmp/fail2ban.test"
|
||||
self.assertEqual(self.__action.actionstart, "touch /tmp/fail2ban.test")
|
||||
self.__action.actionstop = "rm -f /tmp/fail2ban.test"
|
||||
self.assertEqual(self.__action.actionstop, 'rm -f /tmp/fail2ban.test')
|
||||
self.__action.actionban = "echo -n"
|
||||
self.assertEqual(self.__action.actionban, 'echo -n')
|
||||
self.__action.actioncheck = "[ -e /tmp/fail2ban.test ]"
|
||||
self.assertEqual(self.__action.actioncheck, '[ -e /tmp/fail2ban.test ]')
|
||||
self.__action.actionunban = "true"
|
||||
self.assertEqual(self.__action.actionunban, 'true')
|
||||
|
||||
self.assertFalse(self._is_logged('returned'))
|
||||
# no action was actually executed yet
|
||||
|
||||
self.assertTrue(self.__action.execActionBan(None))
|
||||
self.__action.ban({'ip': None})
|
||||
self.assertTrue(self._is_logged('Invariant check failed'))
|
||||
self.assertTrue(self._is_logged('returned successfully'))
|
||||
|
||||
def testExecuteActionEmptyUnban(self):
|
||||
self.__action.setActionUnban("")
|
||||
self.assertTrue(self.__action.execActionUnban(None))
|
||||
self.__action.actionunban = ""
|
||||
self.__action.unban({})
|
||||
self.assertTrue(self._is_logged('Nothing to do'))
|
||||
|
||||
def testExecuteActionStartCtags(self):
|
||||
self.__action.setCInfo("HOST","192.0.2.0")
|
||||
self.__action.setActionStart("touch /tmp/fail2ban.test.<HOST>")
|
||||
self.__action.setActionStop("rm -f /tmp/fail2ban.test.<HOST>")
|
||||
self.__action.setActionCheck("[ -e /tmp/fail2ban.test.192.0.2.0 ]")
|
||||
self.assertTrue(self.__action.execActionStart())
|
||||
self.__action.HOST = "192.0.2.0"
|
||||
self.__action.actionstart = "touch /tmp/fail2ban.test.<HOST>"
|
||||
self.__action.actionstop = "rm -f /tmp/fail2ban.test.<HOST>"
|
||||
self.__action.actioncheck = "[ -e /tmp/fail2ban.test.192.0.2.0 ]"
|
||||
self.__action.start()
|
||||
|
||||
def testExecuteActionCheckRestoreEnvironment(self):
|
||||
self.__action.setActionStart("")
|
||||
self.__action.setActionStop("rm -f /tmp/fail2ban.test")
|
||||
self.__action.setActionBan("rm /tmp/fail2ban.test")
|
||||
self.__action.setActionCheck("[ -e /tmp/fail2ban.test ]")
|
||||
self.assertFalse(self.__action.execActionBan(None))
|
||||
self.__action.actionstart = ""
|
||||
self.__action.actionstop = "rm -f /tmp/fail2ban.test"
|
||||
self.__action.actionban = "rm /tmp/fail2ban.test"
|
||||
self.__action.actioncheck = "[ -e /tmp/fail2ban.test ]"
|
||||
self.assertRaises(RuntimeError, self.__action.ban, {'ip': None})
|
||||
self.assertTrue(self._is_logged('Unable to restore environment'))
|
||||
|
||||
def testExecuteActionChangeCtags(self):
|
||||
self.__action.setCInfo("ROST","192.0.2.0")
|
||||
self.assertEqual(self.__action.getCInfo("ROST"),"192.0.2.0")
|
||||
self.__action.delCInfo("ROST")
|
||||
self.assertRaises(KeyError, self.__action.getCInfo, "ROST")
|
||||
self.assertRaises(AttributeError, getattr, self.__action, "ROST")
|
||||
self.__action.ROST = "192.0.2.0"
|
||||
self.assertEqual(self.__action.ROST,"192.0.2.0")
|
||||
|
||||
def testExecuteActionUnbanAinfo(self):
|
||||
aInfo = {
|
||||
'ABC': "123",
|
||||
}
|
||||
self.__action.setActionBan("touch /tmp/fail2ban.test.123")
|
||||
self.__action.setActionUnban("rm /tmp/fail2ban.test.<ABC>")
|
||||
self.assertTrue(self.__action.execActionBan(None))
|
||||
self.assertTrue(self.__action.execActionUnban(aInfo))
|
||||
self.__action.actionban = "touch /tmp/fail2ban.test.123"
|
||||
self.__action.actionunban = "rm /tmp/fail2ban.test.<ABC>"
|
||||
self.__action.ban(aInfo)
|
||||
self.__action.unban(aInfo)
|
||||
|
||||
def testExecuteActionStartEmpty(self):
|
||||
self.__action.setActionStart("")
|
||||
self.assertTrue(self.__action.execActionStart())
|
||||
self.__action.actionstart = ""
|
||||
self.__action.start()
|
||||
self.assertTrue(self._is_logged('Nothing to do'))
|
||||
|
||||
def testExecuteIncorrectCmd(self):
|
||||
Action.executeCmd('/bin/ls >/dev/null\nbogusXXX now 2>/dev/null')
|
||||
CommandAction.executeCmd('/bin/ls >/dev/null\nbogusXXX now 2>/dev/null')
|
||||
self.assertTrue(self._is_logged('HINT on 127: "Command not found"'))
|
||||
|
||||
def testExecuteTimeout(self):
|
||||
stime = time.time()
|
||||
Action.executeCmd('sleep 60', timeout=2) # Should take a minute
|
||||
# Should take a minute
|
||||
self.assertRaises(
|
||||
RuntimeError, CommandAction.executeCmd, 'sleep 60', timeout=2)
|
||||
self.assertAlmostEqual(time.time() - stime, 2, places=0)
|
||||
self.assertTrue(self._is_logged('sleep 60 -- timed out after 2 seconds'))
|
||||
self.assertTrue(self._is_logged('sleep 60 -- killed with SIGTERM'))
|
||||
|
||||
def testCaptureStdOutErr(self):
|
||||
Action.executeCmd('echo "How now brown cow"')
|
||||
CommandAction.executeCmd('echo "How now brown cow"')
|
||||
self.assertTrue(self._is_logged("'How now brown cow\\n'"))
|
||||
Action.executeCmd(
|
||||
CommandAction.executeCmd(
|
||||
'echo "The rain in Spain stays mainly in the plain" 1>&2')
|
||||
self.assertTrue(self._is_logged(
|
||||
"'The rain in Spain stays mainly in the plain\\n'"))
|
||||
|
||||
def testCallingMap(self):
|
||||
mymap = CallingMap(callme=lambda: str(10), error=lambda: int('a'),
|
||||
dontcallme= "string", number=17)
|
||||
|
||||
# Should work fine
|
||||
self.assertEqual(
|
||||
"%(callme)s okay %(dontcallme)s %(number)i" % mymap,
|
||||
"10 okay string 17")
|
||||
# Error will now trip, demonstrating delayed call
|
||||
self.assertRaises(ValueError, lambda x: "%(error)i" % x, mymap)
|
||||
|
|
|
@ -26,8 +26,8 @@ __license__ = "GPL"
|
|||
|
||||
import unittest
|
||||
|
||||
from fail2ban.server.banmanager import BanManager
|
||||
from fail2ban.server.ticket import BanTicket
|
||||
from ..server.banmanager import BanManager
|
||||
from ..server.ticket import BanTicket
|
||||
|
||||
class AddFailure(unittest.TestCase):
|
||||
|
||||
|
|
|
@ -23,19 +23,22 @@ __license__ = "GPL"
|
|||
|
||||
import os, shutil, sys, tempfile, unittest
|
||||
|
||||
from fail2ban.client.configreader import ConfigReader
|
||||
from fail2ban.client.jailreader import JailReader
|
||||
from fail2ban.client.filterreader import FilterReader
|
||||
from fail2ban.client.jailsreader import JailsReader
|
||||
from fail2ban.client.actionreader import ActionReader
|
||||
from fail2ban.client.configurator import Configurator
|
||||
from fail2ban.tests.utils import LogCaptureTestCase
|
||||
from ..client.configreader import ConfigReader
|
||||
from ..client.jailreader import JailReader
|
||||
from ..client.filterreader import FilterReader
|
||||
from ..client.jailsreader import JailsReader
|
||||
from ..client.actionreader import ActionReader
|
||||
from ..client.configurator import Configurator
|
||||
from .utils import LogCaptureTestCase
|
||||
|
||||
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
||||
if os.path.exists('config/fail2ban.conf'):
|
||||
if os.path.exists(os.path.join('config','fail2ban.conf')):
|
||||
CONFIG_DIR='config'
|
||||
else:
|
||||
CONFIG_DIR='/etc/fail2ban'
|
||||
CONFIG_DIR=os.path.join('etc','fail2ban')
|
||||
|
||||
IMPERFECT_CONFIG = os.path.join('fail2ban', 'tests','config')
|
||||
|
||||
|
||||
IMPERFECT_CONFIG = os.path.join('fail2ban', 'tests','config')
|
||||
|
||||
|
@ -381,13 +384,18 @@ class JailsReaderTest(LogCaptureTestCase):
|
|||
['set', 'brokenaction', 'addaction', 'brokenaction'],
|
||||
['set',
|
||||
'brokenaction',
|
||||
'actionban',
|
||||
'action',
|
||||
'brokenaction',
|
||||
'actionban',
|
||||
'hit with big stick <ip>'],
|
||||
['set', 'brokenaction', 'actionstop', 'brokenaction', ''],
|
||||
['set', 'brokenaction', 'actionstart', 'brokenaction', ''],
|
||||
['set', 'brokenaction', 'actionunban', 'brokenaction', ''],
|
||||
['set', 'brokenaction', 'actioncheck', 'brokenaction', ''],
|
||||
['set', 'brokenaction', 'action', 'brokenaction',
|
||||
'actionstop', ''],
|
||||
['set', 'brokenaction', 'action', 'brokenaction',
|
||||
'actionstart', ''],
|
||||
['set', 'brokenaction', 'action', 'brokenaction',
|
||||
'actionunban', ''],
|
||||
['set', 'brokenaction', 'action', 'brokenaction',
|
||||
'actioncheck', ''],
|
||||
['add', 'parse_to_end_of_jail.conf', 'auto'],
|
||||
['set', 'parse_to_end_of_jail.conf', 'usedns', 'warn'],
|
||||
['set', 'parse_to_end_of_jail.conf', 'maxretry', 3],
|
||||
|
@ -514,7 +522,7 @@ class JailsReaderTest(LogCaptureTestCase):
|
|||
self.assertTrue('blocktype' in action._initOpts)
|
||||
# Verify that we have a call to set it up
|
||||
blocktype_present = False
|
||||
target_command = [ 'set', jail_name, 'setcinfo', action_name, 'blocktype' ]
|
||||
target_command = ['set', jail_name, 'action', action_name, 'blocktype']
|
||||
for command in commands:
|
||||
if (len(command) > 5 and
|
||||
command[:5] == target_command):
|
||||
|
@ -567,6 +575,8 @@ class JailsReaderTest(LogCaptureTestCase):
|
|||
[testjail1]
|
||||
action = testaction1[actname=test1]
|
||||
testaction1[actname=test2]
|
||||
testaction.py
|
||||
testaction.py[actname=test3]
|
||||
filter = testfilter1
|
||||
""")
|
||||
jailfd.close()
|
||||
|
@ -575,8 +585,12 @@ filter = testfilter1
|
|||
self.assertTrue(jails.getOptions())
|
||||
comm_commands = jails.convert(allow_no_files=True)
|
||||
|
||||
action_names = [comm[-1] for comm in comm_commands if comm[:3] == ['set', 'testjail1', 'addaction']]
|
||||
add_actions = [comm[3:] for comm in comm_commands
|
||||
if comm[:3] == ['set', 'testjail1', 'addaction']]
|
||||
|
||||
self.assertNotEqual(len(set(action_names)), 1)
|
||||
self.assertEqual(len(set(action[0] for action in add_actions)), 4)
|
||||
|
||||
# Python actions should not be passed `actname`
|
||||
self.assertEqual(add_actions[-1][-1], "{}")
|
||||
|
||||
shutil.rmtree(basedir)
|
||||
|
|
|
@ -28,11 +28,11 @@ import tempfile
|
|||
import sqlite3
|
||||
import shutil
|
||||
|
||||
from fail2ban.server.database import Fail2BanDb
|
||||
from fail2ban.server.filter import FileContainer
|
||||
from fail2ban.server.mytime import MyTime
|
||||
from fail2ban.server.ticket import FailTicket
|
||||
from fail2ban.tests.dummyjail import DummyJail
|
||||
from ..server.database import Fail2BanDb
|
||||
from ..server.filter import FileContainer
|
||||
from ..server.mytime import MyTime
|
||||
from ..server.ticket import FailTicket
|
||||
from .dummyjail import DummyJail
|
||||
|
||||
class DatabaseTest(unittest.TestCase):
|
||||
|
||||
|
|
|
@ -25,10 +25,10 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
|||
__license__ = "GPL"
|
||||
|
||||
import unittest, calendar, time, datetime, re, pprint
|
||||
from fail2ban.server.datedetector import DateDetector
|
||||
from fail2ban.server.datetemplate import DateTemplate
|
||||
from fail2ban.server.iso8601 import Utc
|
||||
from fail2ban.tests.utils import setUpMyTime, tearDownMyTime
|
||||
from ..server.datedetector import DateDetector
|
||||
from ..server.datetemplate import DateTemplate
|
||||
from ..server.iso8601 import Utc
|
||||
from .utils import setUpMyTime, tearDownMyTime
|
||||
|
||||
class DateDetectorTest(unittest.TestCase):
|
||||
|
||||
|
|
|
@ -26,8 +26,8 @@ __license__ = "GPL"
|
|||
|
||||
import unittest, socket, time, pickle
|
||||
|
||||
from fail2ban.server.failmanager import FailManager, FailManagerEmpty
|
||||
from fail2ban.server.ticket import FailTicket
|
||||
from ..server.failmanager import FailManager, FailManagerEmpty
|
||||
from ..server.ticket import FailTicket
|
||||
|
||||
class AddFailure(unittest.TestCase):
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
from fail2ban.server.action import ActionBase
|
||||
|
||||
class TestAction(ActionBase):
|
||||
|
||||
def __init__(self, jail, name, opt1, opt2=None):
|
||||
super(TestAction, self).__init__(jail, name)
|
||||
self._logSys.debug("%s initialised" % self.__class__.__name__)
|
||||
self.opt1 = opt1
|
||||
self.opt2 = opt2
|
||||
self._opt3 = "Hello"
|
||||
|
||||
def start(self):
|
||||
self._logSys.debug("%s action start" % self.__class__.__name__)
|
||||
|
||||
def stop(self):
|
||||
self._logSys.debug("%s action stop" % self.__class__.__name__)
|
||||
|
||||
def ban(self, aInfo):
|
||||
self._logSys.debug("%s action ban" % self.__class__.__name__)
|
||||
|
||||
def unban(self, aInfo):
|
||||
self._logSys.debug("%s action unban" % self.__class__.__name__)
|
||||
|
||||
def testmethod(self, text):
|
||||
return "%s %s %s" % (self._opt3, text, self.opt1)
|
||||
|
||||
Action = TestAction
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
from fail2ban.server.action import ActionBase
|
||||
|
||||
class TestAction(ActionBase):
|
||||
|
||||
def __init__(self, jail, name):
|
||||
super(TestAction, self).__init__(jail, name)
|
||||
|
||||
def start(self):
|
||||
raise Exception()
|
||||
|
||||
def stop(self):
|
||||
raise Exception()
|
||||
|
||||
def ban(self):
|
||||
raise Exception()
|
||||
|
||||
def unban(self):
|
||||
raise Exception()
|
||||
|
||||
Action = TestAction
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
from fail2ban.server.action import ActionBase
|
||||
|
||||
class TestAction(ActionBase):
|
||||
pass
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
from fail2ban.server.action import ActionBase
|
||||
|
||||
class TestAction():
|
||||
|
||||
def __init__(self, jail, name):
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
pass
|
||||
|
||||
Action = TestAction
|
|
@ -17,4 +17,10 @@
|
|||
# https://github.com/fail2ban/fail2ban/issues/541
|
||||
# failJSON: { "time": "2013-12-30T00:24:50", "match": true , "host": "178.123.108.196" }
|
||||
2013-12-30 00:24:50 1VxPit-000MMd-U4 SA: Action: flagged as Spam but accepted: score=8.2 required=5.0 (scanned in 6/6 secs | Message-Id: 008701cf04ed_24497d70_6cdc7850_@xxx.xx). From <spammer@xxx.xx> (host=ip-4.net-3-2-1.rev.xxx.xx [178.123.108.196]) for trap@example.com
|
||||
# https://github.com/fail2ban/fail2ban/issues/533
|
||||
# failJSON: { "time": "2013-12-29T15:34:12", "match": true , "host": "188.76.45.72" }
|
||||
2013-12-29 15:34:12 1VxHRO-000NiI-Ly SA: Action: silently tossed message: score=31.0 required=5.0 trigger=30.0 (scanned in 6/6 secs | Message-Id: etPan.09bd0c40.c3d5f675.fdf7@server.local). From <Flossiedpd@jazztel.es> (host=72.45.76.188.dynamic.jazztel.es [188.76.45.72]) for me@my.com
|
||||
# https://github.com/fail2ban/fail2ban/issues/533
|
||||
# failJSON: { "time": "2013-12-29T15:39:11", "match": true , "host": "178.123.108.196" }
|
||||
2013-12-29 15:39:11 1VxHWD-000NuW-83 SA: Action: silently tossed message: score=35.8 required=5.0 trigger=30.0 (scanned in 6/6 secs | Message-Id: 1VxHWD-000NuW-83). From <> (host=NULL [178.123.108.196]) for me@my.com
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
# failJSON: { "time": "2004-11-11T18:57:57", "match": true , "host": "203.16.208.190" }
|
||||
Nov 11 18:57:57 HORDE [error] [horde] FAILED LOGIN for graham [203.16.208.190] to Horde [on line 116 of "/home/ace-hosting/public_html/horde/login.php"]
|
||||
|
||||
# failJSON: { "time": "2004-12-15T08:59:59", "match": true , "host": "1.2.3.4" }
|
||||
Dec 15 08:59:59 HORDE [error] [imp] FAILED LOGIN for emai.user@somedomain.com [1.2.3.4] to {mx.somedomain.com:993 [imap/ssl/novalidate-cert]} [pid 68394 on line 139 of /usr/local/www/www.somedomain.com/public_html/horde/imp/lib/Auth/imp.php"]
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# failJSON: { "time": "2013-12-28T19:03:53", "match": true , "host": "178.123.108.196" }
|
||||
Sat Dec 28 19:03:53 2013 - [72926] (178.123.108.196) gsdfg - userinfo error - auth_unix.pl, ret -4, User gsdfg doesn't exist
|
||||
# failJSON: { "time": "2013-12-28T19:04:03", "match": true , "host": "178.123.108.196" }
|
||||
Sat Dec 28 19:04:03 2013 - [72926] (178.123.108.196) gsdfg - login error - no such user - loginname=gsdfg
|
||||
# failJSON: { "time": "2013-12-28T19:05:38", "match": true , "host": "178.123.108.196" }
|
||||
Sat Dec 28 19:05:38 2013 - [73540] (178.123.108.196) myname - login error - auth_unix.pl, ret -4, Password incorrect
|
|
@ -0,0 +1,2 @@
|
|||
# failJSON: { "time": "2011-11-21T14:29:16", "match": true, "host": "10.7.41.61" }
|
||||
2011.11.21 14:29:16 LOG3[28228:140093368055552]: SSL_accept from 10.7.41.61:33454 : 140890C7: error:140890C7:SSL routines:SSL3_GET_CLIENT_CERTIFICATE:peer did not return a certificate
|
|
@ -34,19 +34,16 @@ try:
|
|||
except ImportError:
|
||||
journal = None
|
||||
|
||||
from fail2ban.server.jail import Jail
|
||||
from fail2ban.server.filterpoll import FilterPoll
|
||||
from fail2ban.server.filter import Filter, FileFilter, DNSUtils
|
||||
from fail2ban.server.failmanager import FailManager
|
||||
from fail2ban.server.failmanager import FailManagerEmpty
|
||||
from fail2ban.server.mytime import MyTime
|
||||
from fail2ban.tests.utils import setUpMyTime, tearDownMyTime
|
||||
from fail2ban.tests.utils import mtimesleep, LogCaptureTestCase
|
||||
from ..server.jail import Jail
|
||||
from ..server.filterpoll import FilterPoll
|
||||
from ..server.filter import Filter, FileFilter, DNSUtils
|
||||
from ..server.failmanager import FailManager, FailManagerEmpty
|
||||
from ..server.mytime import MyTime
|
||||
from .utils import setUpMyTime, tearDownMyTime, mtimesleep, LogCaptureTestCase
|
||||
from .dummyjail import DummyJail
|
||||
|
||||
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
||||
|
||||
from fail2ban.tests.dummyjail import DummyJail
|
||||
|
||||
# yoh: per Steven Hiscocks's insight while troubleshooting
|
||||
# https://github.com/fail2ban/fail2ban/issues/103#issuecomment-15542836
|
||||
# adding a sufficiently large buffer might help to guarantee that
|
||||
|
|
|
@ -28,8 +28,8 @@ import shutil
|
|||
|
||||
from glob import glob
|
||||
|
||||
from utils import mbasename, TraceBack, FormatterWithTraceBack
|
||||
from fail2ban.helpers import formatExceptionInfo
|
||||
from .utils import mbasename, TraceBack, FormatterWithTraceBack
|
||||
from ..helpers import formatExceptionInfo
|
||||
|
||||
class HelpersTest(unittest.TestCase):
|
||||
|
||||
|
|
|
@ -30,9 +30,9 @@ else:
|
|||
import simplejson as json
|
||||
next = lambda x: x.next()
|
||||
|
||||
from fail2ban.server.filter import Filter
|
||||
from fail2ban.client.filterreader import FilterReader
|
||||
from fail2ban.tests.utils import setUpMyTime, tearDownMyTime
|
||||
from ..server.filter import Filter
|
||||
from ..client.filterreader import FilterReader
|
||||
from .utils import setUpMyTime, tearDownMyTime
|
||||
|
||||
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
||||
if os.path.exists('config/fail2ban.conf'):
|
||||
|
@ -67,6 +67,7 @@ def testSampleRegexsFactory(name):
|
|||
# Check filter exists
|
||||
filterConf = FilterReader(name, "jail", {}, basedir=CONFIG_DIR)
|
||||
self.assertEqual(filterConf.getFile(), name)
|
||||
self.assertEqual(filterConf.getJailName(), "jail")
|
||||
filterConf.read()
|
||||
filterConf.getOptions({})
|
||||
|
||||
|
|
|
@ -26,11 +26,11 @@ __license__ = "GPL"
|
|||
|
||||
import unittest, socket, time, tempfile, os, locale, sys, logging
|
||||
|
||||
from fail2ban.server.failregex import Regex, FailRegex, RegexException
|
||||
from fail2ban.server.server import Server, logSys
|
||||
from fail2ban.server.jail import Jail
|
||||
from fail2ban.exceptions import UnknownJailException
|
||||
from fail2ban.tests.utils import LogCaptureTestCase
|
||||
from ..server.failregex import Regex, FailRegex, RegexException
|
||||
from ..server.server import Server, logSys
|
||||
from ..server.jail import Jail
|
||||
from ..exceptions import UnknownJailException
|
||||
from .utils import LogCaptureTestCase
|
||||
#from bin.fail2ban-client import Fail2banClient
|
||||
try:
|
||||
from fail2ban.server import filtersystemd
|
||||
|
@ -518,45 +518,38 @@ class Transmitter(TransmitterBase):
|
|||
self.assertEqual(
|
||||
self.transm.proceed(["set", self.jailName, "addaction", action]),
|
||||
(0, action))
|
||||
self.assertEqual(
|
||||
self.transm.proceed(["get", self.jailName, "addaction"]),
|
||||
(0, action))
|
||||
self.assertEqual(
|
||||
self.transm.proceed(
|
||||
["get", self.jailName, "actions"])[1][0].getName(),
|
||||
["get", self.jailName, "actions"])[1][0],
|
||||
action)
|
||||
for cmd, value in zip(cmdList, cmdValueList):
|
||||
self.assertEqual(
|
||||
self.transm.proceed(
|
||||
["set", self.jailName, cmd, action, value]),
|
||||
["set", self.jailName, "action", action, cmd, value]),
|
||||
(0, value))
|
||||
for cmd, value in zip(cmdList, cmdValueList):
|
||||
self.assertEqual(
|
||||
self.transm.proceed(["get", self.jailName, cmd, action]),
|
||||
self.transm.proceed(["get", self.jailName, "action", action, cmd]),
|
||||
(0, value))
|
||||
self.assertEqual(
|
||||
self.transm.proceed(
|
||||
["set", self.jailName, "setcinfo", action, "KEY", "VALUE"]),
|
||||
["set", self.jailName, "action", action, "KEY", "VALUE"]),
|
||||
(0, "VALUE"))
|
||||
self.assertEqual(
|
||||
self.transm.proceed(
|
||||
["get", self.jailName, "cinfo", action, "KEY"]),
|
||||
["get", self.jailName, "action", action, "KEY"]),
|
||||
(0, "VALUE"))
|
||||
self.assertEqual(
|
||||
self.transm.proceed(
|
||||
["get", self.jailName, "cinfo", action, "InvalidKey"])[0],
|
||||
["get", self.jailName, "action", action, "InvalidKey"])[0],
|
||||
1)
|
||||
self.assertEqual(
|
||||
self.transm.proceed(
|
||||
["set", self.jailName, "delcinfo", action, "KEY"]),
|
||||
(0, None))
|
||||
self.assertEqual(
|
||||
self.transm.proceed(
|
||||
["set", self.jailName, "timeout", action, "10"]),
|
||||
["set", self.jailName, "action", action, "timeout", "10"]),
|
||||
(0, 10))
|
||||
self.assertEqual(
|
||||
self.transm.proceed(
|
||||
["get", self.jailName, "timeout", action]),
|
||||
["get", self.jailName, "action", action, "timeout"]),
|
||||
(0, 10))
|
||||
self.assertEqual(
|
||||
self.transm.proceed(["set", self.jailName, "delaction", action]),
|
||||
|
@ -565,6 +558,42 @@ class Transmitter(TransmitterBase):
|
|||
self.transm.proceed(
|
||||
["set", self.jailName, "delaction", "Doesn't exist"])[0],1)
|
||||
|
||||
def testPythonActionMethodsAndProperties(self):
|
||||
action = "TestCaseAction"
|
||||
self.assertEqual(
|
||||
self.transm.proceed(["set", self.jailName, "addaction", action,
|
||||
os.path.join(TEST_FILES_DIR, "action.d", "action.py"),
|
||||
'{"opt1": "value"}']),
|
||||
(0, action))
|
||||
self.assertEqual(
|
||||
sorted(self.transm.proceed(["get", self.jailName,
|
||||
"actionproperties", action])[1]),
|
||||
['opt1', 'opt2'])
|
||||
self.assertEqual(
|
||||
self.transm.proceed(["get", self.jailName, "action", action,
|
||||
"opt1"]),
|
||||
(0, 'value'))
|
||||
self.assertEqual(
|
||||
self.transm.proceed(["get", self.jailName, "action", action,
|
||||
"opt2"]),
|
||||
(0, None))
|
||||
self.assertEqual(
|
||||
sorted(self.transm.proceed(["get", self.jailName, "actionmethods",
|
||||
action])[1]),
|
||||
['ban', 'start', 'stop', 'testmethod', 'unban'])
|
||||
self.assertEqual(
|
||||
self.transm.proceed(["set", self.jailName, "action", action,
|
||||
"testmethod", '{"text": "world!"}']),
|
||||
(0, 'Hello world! value'))
|
||||
self.assertEqual(
|
||||
self.transm.proceed(["set", self.jailName, "action", action,
|
||||
"opt1", "another value"]),
|
||||
(0, 'another value'))
|
||||
self.assertEqual(
|
||||
self.transm.proceed(["set", self.jailName, "action", action,
|
||||
"testmethod", '{"text": "world!"}']),
|
||||
(0, 'Hello world! another value'))
|
||||
|
||||
def testNOK(self):
|
||||
self.assertEqual(self.transm.proceed(["INVALID", "COMMAND"])[0],1)
|
||||
|
||||
|
|
|
@ -26,8 +26,8 @@ __license__ = "GPL"
|
|||
|
||||
import unittest, time, tempfile, os, threading
|
||||
|
||||
from fail2ban.server.asyncserver import AsyncServer, AsyncServerException
|
||||
from fail2ban.client.csocket import CSocket
|
||||
from ..server.asyncserver import AsyncServer, AsyncServerException
|
||||
from ..client.csocket import CSocket
|
||||
|
||||
class Socket(unittest.TestCase):
|
||||
|
||||
|
|
|
@ -25,16 +25,9 @@ __license__ = "GPL"
|
|||
import logging, os, re, traceback, time, unittest, sys
|
||||
from os.path import basename, dirname
|
||||
from StringIO import StringIO
|
||||
|
||||
if sys.version_info >= (2, 6):
|
||||
import json
|
||||
else:
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
json = None
|
||||
|
||||
from fail2ban.server.mytime import MyTime
|
||||
from ..server.mytime import MyTime
|
||||
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
||||
|
@ -138,19 +131,18 @@ def tearDownMyTime():
|
|||
def gatherTests(regexps=None, no_network=False):
|
||||
# Import all the test cases here instead of a module level to
|
||||
# avoid circular imports
|
||||
from fail2ban.tests import banmanagertestcase
|
||||
from fail2ban.tests import clientreadertestcase
|
||||
from fail2ban.tests import failmanagertestcase
|
||||
from fail2ban.tests import filtertestcase
|
||||
from fail2ban.tests import servertestcase
|
||||
from fail2ban.tests import datedetectortestcase
|
||||
from fail2ban.tests import actiontestcase
|
||||
from fail2ban.tests import actionstestcase
|
||||
from fail2ban.tests import sockettestcase
|
||||
from fail2ban.tests import misctestcase
|
||||
from fail2ban.tests import databasetestcase
|
||||
if json:
|
||||
from fail2ban.tests import samplestestcase
|
||||
from . import banmanagertestcase
|
||||
from . import clientreadertestcase
|
||||
from . import failmanagertestcase
|
||||
from . import filtertestcase
|
||||
from . import servertestcase
|
||||
from . import datedetectortestcase
|
||||
from . import actiontestcase
|
||||
from . import actionstestcase
|
||||
from . import sockettestcase
|
||||
from . import misctestcase
|
||||
from . import databasetestcase
|
||||
from . import samplestestcase
|
||||
|
||||
if not regexps: # pragma: no cover
|
||||
tests = unittest.TestSuite()
|
||||
|
@ -172,7 +164,7 @@ def gatherTests(regexps=None, no_network=False):
|
|||
tests.addTest(unittest.makeSuite(servertestcase.Transmitter))
|
||||
tests.addTest(unittest.makeSuite(servertestcase.JailTests))
|
||||
tests.addTest(unittest.makeSuite(servertestcase.RegexTests))
|
||||
tests.addTest(unittest.makeSuite(actiontestcase.ExecuteAction))
|
||||
tests.addTest(unittest.makeSuite(actiontestcase.CommandActionTest))
|
||||
tests.addTest(unittest.makeSuite(actionstestcase.ExecuteActions))
|
||||
# FailManager
|
||||
tests.addTest(unittest.makeSuite(failmanagertestcase.AddFailure))
|
||||
|
@ -207,30 +199,27 @@ def gatherTests(regexps=None, no_network=False):
|
|||
|
||||
# DateDetector
|
||||
tests.addTest(unittest.makeSuite(datedetectortestcase.DateDetectorTest))
|
||||
if json:
|
||||
# Filter Regex tests with sample logs
|
||||
tests.addTest(unittest.makeSuite(samplestestcase.FilterSamplesRegex))
|
||||
else:
|
||||
logSys.warning("I: Skipping filter samples testing. No simplejson/json module")
|
||||
|
||||
#
|
||||
# Extensive use-tests of different available filters backends
|
||||
#
|
||||
|
||||
from fail2ban.server.filterpoll import FilterPoll
|
||||
from ..server.filterpoll import FilterPoll
|
||||
filters = [FilterPoll] # always available
|
||||
|
||||
# Additional filters available only if external modules are available
|
||||
# yoh: Since I do not know better way for parametric tests
|
||||
# with good old unittest
|
||||
try:
|
||||
from fail2ban.server.filtergamin import FilterGamin
|
||||
from ..server.filtergamin import FilterGamin
|
||||
filters.append(FilterGamin)
|
||||
except Exception, e: # pragma: no cover
|
||||
logSys.warning("Skipping gamin backend testing. Got exception '%s'" % e)
|
||||
|
||||
try:
|
||||
from fail2ban.server.filterpyinotify import FilterPyinotify
|
||||
from ..server.filterpyinotify import FilterPyinotify
|
||||
filters.append(FilterPyinotify)
|
||||
except Exception, e: # pragma: no cover
|
||||
logSys.warning("I: Skipping pyinotify backend testing. Got exception '%s'" % e)
|
||||
|
@ -239,7 +228,7 @@ def gatherTests(regexps=None, no_network=False):
|
|||
tests.addTest(unittest.makeSuite(
|
||||
filtertestcase.get_monitor_failures_testcase(Filter_)))
|
||||
try: # pragma: systemd no cover
|
||||
from fail2ban.server.filtersystemd import FilterSystemd
|
||||
from ..server.filtersystemd import FilterSystemd
|
||||
tests.addTest(unittest.makeSuite(filtertestcase.get_monitor_failures_journal_testcase(FilterSystemd)))
|
||||
except Exception, e: # pragma: no cover
|
||||
logSys.warning("I: Skipping systemd backend testing. Got exception '%s'" % e)
|
||||
|
|
|
@ -7,7 +7,7 @@ jail.conf \- configuration for the fail2ban server
|
|||
|
||||
.I jail.conf / jail.local
|
||||
|
||||
.I action.d/*.conf action.d/*.local
|
||||
.I action.d/*.conf action.d/*.local action.d/*.py
|
||||
|
||||
.I filter.d/*.conf filter.d/*.local
|
||||
.SH DESCRIPTION
|
||||
|
@ -113,13 +113,15 @@ uses systemd python library to access the systemd journal. Specifying \fBlogpath
|
|||
will try to use the following backends, in order: pyinotify, gamin, polling
|
||||
.PP
|
||||
.SS Actions
|
||||
Each jail can be configured with only a single filter, but may have multiple actions. By default, the name of a action is the action filename. In the case where multiple of the same action are to be used, the \fBactname\fR option can be assigned to the action to avoid duplicatione.g.:
|
||||
Each jail can be configured with only a single filter, but may have multiple actions. By default, the name of a action is the action filename, and in the case of Python actions, the ".py" file extension is stripped. Where multiple of the same action are to be used, the \fBactname\fR option can be assigned to the action to avoid duplication e.g.:
|
||||
.PP
|
||||
.nf
|
||||
[ssh-iptables-ipset]
|
||||
enabled = true
|
||||
action = sendmail[name=ssh, dest=john@example.com, actname=mail-john]
|
||||
sendmail[name=ssh, dest=paul@example.com, actname=mail-paul]
|
||||
smtp.py[dest=chris@example.com, actname=smtp-chris]
|
||||
smtp.py[dest=sally@example.com, actname=smtp-sally]
|
||||
.fi
|
||||
|
||||
.SH "ACTION FILES"
|
||||
|
@ -130,8 +132,6 @@ and override the required settings.
|
|||
|
||||
Action files are ini files that have two sections, \fBDefinition\fR and \fBInit\fR .
|
||||
|
||||
The [Init] section allows for action-specific settings. In \fIjail.conf/jail.local\fR these can be overwritten for a particular jail as options to the jail.
|
||||
|
||||
The following commands can be present in the [Definition] section.
|
||||
.TP
|
||||
\fBactionstart\fR
|
||||
|
@ -148,12 +148,16 @@ command(s) that bans the IP address after \fBmaxretry\fR log lines matches withi
|
|||
.TP
|
||||
\fBactionunban\fR
|
||||
command(s) that unbans the IP address after \fBbantime\fR.
|
||||
|
||||
.PP
|
||||
The [Init] section allows for action-specific settings. In \fIjail.conf/jail.local\fR these can be overwritten for a particular jail as options to the jail. The following are special tags which can be set in the [Init] section:
|
||||
.TP
|
||||
\fBtimeout\fR
|
||||
The maximum period of time in seconds that a command can executed, before being killed.
|
||||
.PP
|
||||
Commands specified in the [Definition] section are executed through a system shell so shell redirection and process control is allowed. The commands should
|
||||
return 0, otherwise error would be logged. Moreover if \fBactioncheck\fR exits with non-0 status, it is taken as indication that firewall status has changed and fail2ban needs to reinitialize itself (i.e. issue \fBactionstop\fR and \fBactionstart\fR commands).
|
||||
|
||||
Tags are enclosed in <>. All the elements of [Init] are tags that are replaced in all action commands. Tags can be added by the
|
||||
\fBfail2ban-client\fR using the setctag command. \fB<br>\fR is a tag that is always a new line (\\n).
|
||||
\fBfail2ban-client\fR using the "set <JAIL> action <ACT>" command. \fB<br>\fR is a tag that is always a new line (\\n).
|
||||
|
||||
More than a single command is allowed to be specified. Each command needs to be on a separate line and indented with whitespaces without blank lines. The following example defines
|
||||
two commands to be executed.
|
||||
|
@ -188,6 +192,8 @@ As per \fBmatches\fR, but includes all lines for the IP which are contained with
|
|||
\fBipjailmatches\fR
|
||||
As per \fBipmatches\fR, but matches are limited for the IP and for the current jail.
|
||||
|
||||
.SH "PYTHON ACTION FILES"
|
||||
Python based actions can also be used, where the file name must be \fI[actionname].py\fR. The Python file must contain a variable \fIAction\fR which points to Python class. This class must implement a minimum interface as described by \fIfail2ban.server.action.ActionBase\fR, which can be inherited from to ease implementation.
|
||||
.SH FILTER FILES
|
||||
|
||||
Filter definitions are those in \fI/etc/fail2ban/filter.d/*.conf\fR and \fIfilter.d/*.local\fR.
|
||||
|
|
Loading…
Reference in New Issue