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.
|
into logging messages in case of error or at DEBUG loglevel.
|
||||||
* Added action xarf-login-attack to report formatted attack messages
|
* Added action xarf-login-attack to report formatted attack messages
|
||||||
according to the XARF standard (v0.2). Close gh-105
|
according to the XARF standard (v0.2). Close gh-105
|
||||||
* Add filter for apache-modsecurity
|
|
||||||
* Support PyPy
|
* Support PyPy
|
||||||
|
* Filter for stunnel
|
||||||
|
|
||||||
- Enhancements
|
- 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 filter for sendmail-spam. Close gh-418
|
||||||
* Multiline regex for Disconnecting: Too many authentication failures for
|
* Multiline regex for Disconnecting: Too many authentication failures for
|
||||||
root [preauth]\nConnection closed by 6X.XXX.XXX.XXX [preauth]
|
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 firewall-cmd actioncheck - patch from Adam Tkac. Redhat Bug #979622
|
||||||
- Fix apache-common for apache-2.4 log file format. Thanks Mark White.
|
- Fix apache-common for apache-2.4 log file format. Thanks Mark White.
|
||||||
Closes gh-516
|
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:
|
- Enhancements:
|
||||||
|
- added firewallcmd-ipset action
|
||||||
- long names on jails documented based on iptables limit of 30 less
|
- long names on jails documented based on iptables limit of 30 less
|
||||||
len("fail2ban-").
|
len("fail2ban-").
|
||||||
- remove indentation of name and loglevel while logging to SYSLOG to
|
- 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.
|
- updated check_fail2ban to return performance data for all jails.
|
||||||
- filter apache-noscript now includes php cgi scripts.
|
- filter apache-noscript now includes php cgi scripts.
|
||||||
Thanks dani. Closes gh-503
|
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:
|
- New Features:
|
||||||
|
|
||||||
* filter.d/solid-pop3d -- added thanks to Jacques Lav!gnotte on mailinglist.
|
- Added filter for solid-pop3d -- thanks to Jacques Lav!gnotte on mailinglist.
|
||||||
* filter.d/nsd.conf -- also amended Unix date template to match nsd format
|
- Added filter for apache-modsecurity
|
||||||
|
- Added filter for openwebmail thanks Ivo Truxa. Closes gh-543
|
||||||
- Enhancements:
|
- Added filter for horde
|
||||||
- loglines now also report "[PID]" after the name portion
|
|
||||||
|
|
||||||
ver. 0.8.11 (2013/11/13) - loves-unittests-and-tight-DoS-free-filter-regexes
|
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 a change to the relevant section of the ChangeLog; and
|
||||||
* Include yourself in THANKS if not already there.
|
* Include yourself in THANKS if not already there.
|
||||||
|
|
||||||
Filters
|
If you are developing filters see the FILTERS file for documentation.
|
||||||
=======
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
|
|
||||||
Code Testing
|
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
|
THANKS
|
||||||
COPYING
|
COPYING
|
||||||
DEVELOP
|
DEVELOP
|
||||||
|
FILTERS
|
||||||
fail2ban-2to3
|
fail2ban-2to3
|
||||||
fail2ban-testcases-all
|
fail2ban-testcases-all
|
||||||
fail2ban-testcases-all-python3
|
fail2ban-testcases-all-python3
|
||||||
|
@ -25,6 +26,7 @@ fail2ban/client/__init__.py
|
||||||
fail2ban/client/configurator.py
|
fail2ban/client/configurator.py
|
||||||
fail2ban/client/csocket.py
|
fail2ban/client/csocket.py
|
||||||
fail2ban/server/asyncserver.py
|
fail2ban/server/asyncserver.py
|
||||||
|
fail2ban/server/database.py
|
||||||
fail2ban/server/filter.py
|
fail2ban/server/filter.py
|
||||||
fail2ban/server/filterpyinotify.py
|
fail2ban/server/filterpyinotify.py
|
||||||
fail2ban/server/filtergamin.py
|
fail2ban/server/filtergamin.py
|
||||||
|
@ -62,6 +64,10 @@ fail2ban/tests/sockettestcase.py
|
||||||
fail2ban/tests/utils.py
|
fail2ban/tests/utils.py
|
||||||
fail2ban/tests/misctestcase.py
|
fail2ban/tests/misctestcase.py
|
||||||
fail2ban/tests/databasetestcase.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/.htaccess
|
||||||
fail2ban/tests/files/config/apache-auth/digest/.htpasswd
|
fail2ban/tests/files/config/apache-auth/digest/.htpasswd
|
||||||
fail2ban/tests/files/config/apache-auth/digest_time/.htaccess
|
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/sendmail-spam
|
||||||
fail2ban/tests/files/logs/sieve
|
fail2ban/tests/files/logs/sieve
|
||||||
fail2ban/tests/files/logs/squid
|
fail2ban/tests/files/logs/squid
|
||||||
|
fail2ban/tests/files/logs/stunnel
|
||||||
fail2ban/tests/files/logs/suhosin
|
fail2ban/tests/files/logs/suhosin
|
||||||
fail2ban/tests/files/logs/sogo-auth
|
fail2ban/tests/files/logs/sogo-auth
|
||||||
fail2ban/tests/files/logs/solid-pop3d
|
fail2ban/tests/files/logs/solid-pop3d
|
||||||
|
@ -156,6 +163,7 @@ setup.py
|
||||||
setup.cfg
|
setup.cfg
|
||||||
kill-server
|
kill-server
|
||||||
config/jail.conf
|
config/jail.conf
|
||||||
|
config/fail2ban.conf
|
||||||
config/filter.d/common.conf
|
config/filter.d/common.conf
|
||||||
config/filter.d/apache-auth.conf
|
config/filter.d/apache-auth.conf
|
||||||
config/filter.d/apache-badbots.conf
|
config/filter.d/apache-badbots.conf
|
||||||
|
@ -170,17 +178,22 @@ config/filter.d/exim.conf
|
||||||
config/filter.d/gssftpd.conf
|
config/filter.d/gssftpd.conf
|
||||||
config/filter.d/suhosin.conf
|
config/filter.d/suhosin.conf
|
||||||
config/filter.d/named-refused.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/postfix.conf
|
||||||
config/filter.d/proftpd.conf
|
config/filter.d/proftpd.conf
|
||||||
config/filter.d/pure-ftpd.conf
|
config/filter.d/pure-ftpd.conf
|
||||||
config/filter.d/qmail.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/sieve.conf
|
||||||
config/filter.d/solid-pop3d.conf
|
config/filter.d/solid-pop3d.conf
|
||||||
config/filter.d/sshd.conf
|
config/filter.d/sshd.conf
|
||||||
config/filter.d/sshd-ddos.conf
|
config/filter.d/sshd-ddos.conf
|
||||||
|
config/filter.d/stunnel.conf
|
||||||
config/filter.d/vsftpd.conf
|
config/filter.d/vsftpd.conf
|
||||||
config/filter.d/webmin-auth.conf
|
config/filter.d/webmin-auth.conf
|
||||||
config/filter.d/wuftpd.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/sendmail-common.conf
|
||||||
config/action.d/bsd-ipfw.conf
|
config/action.d/bsd-ipfw.conf
|
||||||
config/action.d/dummy.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-ipset-proto6-allports.conf
|
||||||
config/action.d/iptables-blocktype.conf
|
config/action.d/iptables-blocktype.conf
|
||||||
config/action.d/iptables-ipset-proto4.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.conf
|
||||||
config/action.d/sendmail-whois-lines.conf
|
config/action.d/sendmail-whois-lines.conf
|
||||||
config/action.d/shorewall.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
|
doc/run-rootless.txt
|
||||||
man/fail2ban-client.1
|
man/fail2ban-client.1
|
||||||
man/fail2ban.1
|
man/fail2ban.1
|
||||||
|
|
2
THANKS
2
THANKS
|
@ -35,9 +35,11 @@ ftoppi
|
||||||
François Boulogne
|
François Boulogne
|
||||||
Frédéric
|
Frédéric
|
||||||
Georgiy Mernov
|
Georgiy Mernov
|
||||||
|
Guilhem Lettron
|
||||||
Guillaume Delvit
|
Guillaume Delvit
|
||||||
Hanno 'Rince' Wagner
|
Hanno 'Rince' Wagner
|
||||||
Iain Lea
|
Iain Lea
|
||||||
|
Ivo Truxa
|
||||||
John Thoe
|
John Thoe
|
||||||
Jacques Lav!gnotte
|
Jacques Lav!gnotte
|
||||||
Ioan Indreias
|
Ioan Indreias
|
||||||
|
|
|
@ -41,7 +41,7 @@ except ImportError:
|
||||||
journal = None
|
journal = None
|
||||||
|
|
||||||
from fail2ban.version import version
|
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.filter import Filter
|
||||||
from fail2ban.server.failregex import RegexException
|
from fail2ban.server.failregex import RegexException
|
||||||
|
|
||||||
|
@ -206,8 +206,6 @@ class LineStats(object):
|
||||||
|
|
||||||
class Fail2banRegex(object):
|
class Fail2banRegex(object):
|
||||||
|
|
||||||
CONFIG_DEFAULTS = {'configpath' : "/etc/fail2ban/"}
|
|
||||||
|
|
||||||
def __init__(self, opts):
|
def __init__(self, opts):
|
||||||
self._verbose = opts.verbose
|
self._verbose = opts.verbose
|
||||||
self._debuggex = opts.debuggex
|
self._debuggex = opts.debuggex
|
||||||
|
@ -257,46 +255,37 @@ class Fail2banRegex(object):
|
||||||
assert(regextype in ('fail', 'ignore'))
|
assert(regextype in ('fail', 'ignore'))
|
||||||
regex = regextype + 'regex'
|
regex = regextype + 'regex'
|
||||||
if os.path.isfile(value):
|
if os.path.isfile(value):
|
||||||
reader = SafeConfigParserWithIncludes(defaults=self.CONFIG_DEFAULTS)
|
print "Use %11s file : %s" % (regex, value)
|
||||||
try:
|
reader = FilterReader(value, 'fail2ban-regex-jail', {})
|
||||||
reader.read(value)
|
reader.setBaseDir(None)
|
||||||
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
|
|
||||||
|
|
||||||
# Read out and set possible value of maxlines
|
if reader.readexplicit():
|
||||||
try:
|
reader.getOptions(None)
|
||||||
maxlines = reader.get("Init", "maxlines")
|
readercommands = reader.convert()
|
||||||
except (NoSectionError, NoOptionError):
|
regex_values = [
|
||||||
# No [Init].maxlines found.
|
RegexStat(m[3])
|
||||||
pass
|
for m in filter(
|
||||||
|
lambda x: x[0] == 'set' and x[2] == "add%sregex" % regextype,
|
||||||
|
readercommands)]
|
||||||
|
# Read out and set possible value of maxlines
|
||||||
|
for command in readercommands:
|
||||||
|
if command[2] == "maxlines":
|
||||||
|
maxlines = int(command[3])
|
||||||
|
try:
|
||||||
|
self.setMaxLines(maxlines)
|
||||||
|
except ValueError:
|
||||||
|
print "ERROR: Invalid value for maxlines (%(maxlines)r) " \
|
||||||
|
"read from %(value)s" % locals()
|
||||||
|
return False
|
||||||
|
elif command[2] == 'addjournalmatch':
|
||||||
|
journalmatch = command[3]
|
||||||
|
self.setJournalMatch(shlex.split(journalmatch))
|
||||||
|
elif command[2] == 'datepattern':
|
||||||
|
datepattern = command[3]
|
||||||
|
self.setDatePattern(datepattern)
|
||||||
else:
|
else:
|
||||||
try:
|
print "ERROR: failed to read %s" % value
|
||||||
self.setMaxLines(maxlines)
|
return False
|
||||||
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:
|
|
||||||
self.setJournalMatch(shlex.split(journalmatch))
|
|
||||||
else:
|
else:
|
||||||
print "Use %11s line : %s" % (regex, shortstr(value))
|
print "Use %11s line : %s" % (regex, shortstr(value))
|
||||||
regex_values = [RegexStat(value)]
|
regex_values = [RegexStat(value)]
|
||||||
|
|
|
@ -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]
|
[Definition]
|
||||||
|
|
||||||
actionstart = firewall-cmd --direct --add-chain ipv4 filter fail2ban-<name>
|
actionstart = firewall-cmd --direct --add-chain ipv4 filter f2b-<name>
|
||||||
firewall-cmd --direct --add-rule ipv4 filter fail2ban-<name> 1000 -j RETURN
|
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 fail2ban-<name>
|
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>
|
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 fail2ban-<name>
|
firewall-cmd --direct --remove-rules ipv4 filter f2b-<name>
|
||||||
firewall-cmd --direct --remove-chain ipv4 filter fail2ban-<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]
|
[Init]
|
||||||
|
|
||||||
|
|
|
@ -17,23 +17,23 @@ before = iptables-blocktype.conf
|
||||||
# Notes.: command executed once at the start of Fail2Ban.
|
# Notes.: command executed once at the start of Fail2Ban.
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstart = iptables -N fail2ban-<name>
|
actionstart = iptables -N f2b-<name>
|
||||||
iptables -A fail2ban-<name> -j RETURN
|
iptables -A f2b-<name> -j RETURN
|
||||||
iptables -I <chain> -p <protocol> -j fail2ban-<name>
|
iptables -I <chain> -p <protocol> -j f2b-<name>
|
||||||
|
|
||||||
# Option: actionstop
|
# Option: actionstop
|
||||||
# Notes.: command executed once at the end of Fail2Ban
|
# Notes.: command executed once at the end of Fail2Ban
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstop = iptables -D <chain> -p <protocol> -j fail2ban-<name>
|
actionstop = iptables -D <chain> -p <protocol> -j f2b-<name>
|
||||||
iptables -F fail2ban-<name>
|
iptables -F f2b-<name>
|
||||||
iptables -X fail2ban-<name>
|
iptables -X f2b-<name>
|
||||||
|
|
||||||
# Option: actioncheck
|
# Option: actioncheck
|
||||||
# Notes.: command executed once before each actionban command
|
# Notes.: command executed once before each actionban command
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actioncheck = iptables -n -L <chain> | grep -q 'fail2ban-<name>[ \t]'
|
actioncheck = iptables -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||||
|
|
||||||
# Option: actionban
|
# Option: actionban
|
||||||
# Notes.: command executed when banning an IP. Take care that the
|
# 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
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionban = iptables -I fail2ban-<name> 1 -s <ip> -j <blocktype>
|
actionban = iptables -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||||
|
|
||||||
# Option: actionunban
|
# Option: actionunban
|
||||||
# Notes.: command executed when unbanning an IP. Take care that the
|
# Notes.: command executed when unbanning an IP. Take care that the
|
||||||
|
@ -49,7 +49,7 @@ actionban = iptables -I fail2ban-<name> 1 -s <ip> -j <blocktype>
|
||||||
# Tags: See jail.conf(5) man page
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionunban = iptables -D fail2ban-<name> -s <ip> -j <blocktype>
|
actionunban = iptables -D f2b-<name> -s <ip> -j <blocktype>
|
||||||
|
|
||||||
[Init]
|
[Init]
|
||||||
|
|
||||||
|
|
|
@ -27,16 +27,16 @@ before = iptables-blocktype.conf
|
||||||
# Notes.: command executed once at the start of Fail2Ban.
|
# Notes.: command executed once at the start of Fail2Ban.
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstart = ipset --create fail2ban-<name> iphash
|
actionstart = ipset --create f2b-<name> iphash
|
||||||
iptables -I INPUT -p <protocol> -m multiport --dports <port> -m set --match-set fail2ban-<name> src -j <blocktype>
|
iptables -I INPUT -p <protocol> -m multiport --dports <port> -m set --match-set f2b-<name> src -j <blocktype>
|
||||||
|
|
||||||
# Option: actionstop
|
# Option: actionstop
|
||||||
# Notes.: command executed once at the end of Fail2Ban
|
# Notes.: command executed once at the end of Fail2Ban
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstop = iptables -D INPUT -p <protocol> -m multiport --dports <port> -m set --match-set fail2ban-<name> src -j <blocktype>
|
actionstop = iptables -D INPUT -p <protocol> -m multiport --dports <port> -m set --match-set f2b-<name> src -j <blocktype>
|
||||||
ipset --flush fail2ban-<name>
|
ipset --flush f2b-<name>
|
||||||
ipset --destroy fail2ban-<name>
|
ipset --destroy f2b-<name>
|
||||||
|
|
||||||
# Option: actionban
|
# Option: actionban
|
||||||
# Notes.: command executed when banning an IP. Take care that the
|
# 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
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# 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
|
# Option: actionunban
|
||||||
# Notes.: command executed when unbanning an IP. Take care that the
|
# 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
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# 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]
|
[Init]
|
||||||
|
|
||||||
|
|
|
@ -24,16 +24,16 @@ before = iptables-blocktype.conf
|
||||||
# Notes.: command executed once at the start of Fail2Ban.
|
# Notes.: command executed once at the start of Fail2Ban.
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstart = ipset create fail2ban-<name> hash:ip timeout <bantime>
|
actionstart = ipset create f2b-<name> hash:ip timeout <bantime>
|
||||||
iptables -I INPUT -m set --match-set fail2ban-<name> src -j <blocktype>
|
iptables -I INPUT -m set --match-set f2b-<name> src -j <blocktype>
|
||||||
|
|
||||||
# Option: actionstop
|
# Option: actionstop
|
||||||
# Notes.: command executed once at the end of Fail2Ban
|
# Notes.: command executed once at the end of Fail2Ban
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstop = iptables -D INPUT -m set --match-set fail2ban-<name> src -j <blocktype>
|
actionstop = iptables -D INPUT -m set --match-set f2b-<name> src -j <blocktype>
|
||||||
ipset flush fail2ban-<name>
|
ipset flush f2b-<name>
|
||||||
ipset destroy fail2ban-<name>
|
ipset destroy f2b-<name>
|
||||||
|
|
||||||
# Option: actionban
|
# Option: actionban
|
||||||
# Notes.: command executed when banning an IP. Take care that the
|
# 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
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionban = ipset add fail2ban-<name> <ip> timeout <bantime> -exist
|
actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist
|
||||||
|
|
||||||
# Option: actionunban
|
# Option: actionunban
|
||||||
# Notes.: command executed when unbanning an IP. Take care that the
|
# Notes.: command executed when unbanning an IP. Take care that the
|
||||||
|
@ -49,7 +49,7 @@ actionban = ipset add fail2ban-<name> <ip> timeout <bantime> -exist
|
||||||
# Tags: See jail.conf(5) man page
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionunban = ipset del fail2ban-<name> <ip> -exist
|
actionunban = ipset del f2b-<name> <ip> -exist
|
||||||
|
|
||||||
[Init]
|
[Init]
|
||||||
|
|
||||||
|
|
|
@ -24,16 +24,16 @@ before = iptables-blocktype.conf
|
||||||
# Notes.: command executed once at the start of Fail2Ban.
|
# Notes.: command executed once at the start of Fail2Ban.
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstart = ipset create fail2ban-<name> hash:ip timeout <bantime>
|
actionstart = ipset create f2b-<name> hash:ip timeout <bantime>
|
||||||
iptables -I INPUT -p <protocol> -m multiport --dports <port> -m set --match-set fail2ban-<name> src -j <blocktype>
|
iptables -I INPUT -p <protocol> -m multiport --dports <port> -m set --match-set f2b-<name> src -j <blocktype>
|
||||||
|
|
||||||
# Option: actionstop
|
# Option: actionstop
|
||||||
# Notes.: command executed once at the end of Fail2Ban
|
# Notes.: command executed once at the end of Fail2Ban
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstop = iptables -D INPUT -p <protocol> -m multiport --dports <port> -m set --match-set fail2ban-<name> src -j <blocktype>
|
actionstop = iptables -D INPUT -p <protocol> -m multiport --dports <port> -m set --match-set f2b-<name> src -j <blocktype>
|
||||||
ipset flush fail2ban-<name>
|
ipset flush f2b-<name>
|
||||||
ipset destroy fail2ban-<name>
|
ipset destroy f2b-<name>
|
||||||
|
|
||||||
# Option: actionban
|
# Option: actionban
|
||||||
# Notes.: command executed when banning an IP. Take care that the
|
# 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
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionban = ipset add fail2ban-<name> <ip> timeout <bantime> -exist
|
actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist
|
||||||
|
|
||||||
# Option: actionunban
|
# Option: actionunban
|
||||||
# Notes.: command executed when unbanning an IP. Take care that the
|
# Notes.: command executed when unbanning an IP. Take care that the
|
||||||
|
@ -49,7 +49,7 @@ actionban = ipset add fail2ban-<name> <ip> timeout <bantime> -exist
|
||||||
# Tags: See jail.conf(5) man page
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionunban = ipset del fail2ban-<name> <ip> -exist
|
actionunban = ipset del f2b-<name> <ip> -exist
|
||||||
|
|
||||||
[Init]
|
[Init]
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
# Author: Guido Bozzetto
|
# Author: Guido Bozzetto
|
||||||
# Modified: Cyril Jaquier
|
# Modified: Cyril Jaquier
|
||||||
#
|
#
|
||||||
# make "fail2ban-<name>" chain to match drop IP
|
# make "f2b-<name>" chain to match drop IP
|
||||||
# make "fail2ban-<name>-log" chain to log and drop
|
# make "f2b-<name>-log" chain to log and drop
|
||||||
# insert a jump to fail2ban-<name> from -I <chain> if proto/port match
|
# 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.
|
# Notes.: command executed once at the start of Fail2Ban.
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstart = iptables -N fail2ban-<name>
|
actionstart = iptables -N f2b-<name>
|
||||||
iptables -A fail2ban-<name> -j RETURN
|
iptables -A f2b-<name> -j RETURN
|
||||||
iptables -I <chain> 1 -p <protocol> -m multiport --dports <port> -j fail2ban-<name>
|
iptables -I <chain> 1 -p <protocol> -m multiport --dports <port> -j f2b-<name>
|
||||||
iptables -N fail2ban-<name>-log
|
iptables -N f2b-<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 -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 fail2ban-<name>-log -j <blocktype>
|
iptables -A f2b-<name>-log -j <blocktype>
|
||||||
|
|
||||||
# Option: actionstop
|
# Option: actionstop
|
||||||
# Notes.: command executed once at the end of Fail2Ban
|
# Notes.: command executed once at the end of Fail2Ban
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstop = iptables -D <chain> -p <protocol> -m multiport --dports <port> -j fail2ban-<name>
|
actionstop = iptables -D <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
|
||||||
iptables -F fail2ban-<name>
|
iptables -F f2b-<name>
|
||||||
iptables -F fail2ban-<name>-log
|
iptables -F f2b-<name>-log
|
||||||
iptables -X fail2ban-<name>
|
iptables -X f2b-<name>
|
||||||
iptables -X fail2ban-<name>-log
|
iptables -X f2b-<name>-log
|
||||||
|
|
||||||
# Option: actioncheck
|
# Option: actioncheck
|
||||||
# Notes.: command executed once before each actionban command
|
# Notes.: command executed once before each actionban command
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actioncheck = iptables -n -L fail2ban-<name>-log >/dev/null
|
actioncheck = iptables -n -L f2b-<name>-log >/dev/null
|
||||||
|
|
||||||
# Option: actionban
|
# Option: actionban
|
||||||
# Notes.: command executed when banning an IP. Take care that the
|
# 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
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# 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
|
# Option: actionunban
|
||||||
# Notes.: command executed when unbanning an IP. Take care that the
|
# 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
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# 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]
|
[Init]
|
||||||
|
|
||||||
|
|
|
@ -14,23 +14,23 @@ before = iptables-blocktype.conf
|
||||||
# Notes.: command executed once at the start of Fail2Ban.
|
# Notes.: command executed once at the start of Fail2Ban.
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstart = iptables -N fail2ban-<name>
|
actionstart = iptables -N f2b-<name>
|
||||||
iptables -A fail2ban-<name> -j RETURN
|
iptables -A f2b-<name> -j RETURN
|
||||||
iptables -I <chain> -p <protocol> -m multiport --dports <port> -j fail2ban-<name>
|
iptables -I <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
|
||||||
|
|
||||||
# Option: actionstop
|
# Option: actionstop
|
||||||
# Notes.: command executed once at the end of Fail2Ban
|
# Notes.: command executed once at the end of Fail2Ban
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstop = iptables -D <chain> -p <protocol> -m multiport --dports <port> -j fail2ban-<name>
|
actionstop = iptables -D <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
|
||||||
iptables -F fail2ban-<name>
|
iptables -F f2b-<name>
|
||||||
iptables -X fail2ban-<name>
|
iptables -X f2b-<name>
|
||||||
|
|
||||||
# Option: actioncheck
|
# Option: actioncheck
|
||||||
# Notes.: command executed once before each actionban command
|
# Notes.: command executed once before each actionban command
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actioncheck = iptables -n -L <chain> | grep -q 'fail2ban-<name>[ \t]'
|
actioncheck = iptables -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||||
|
|
||||||
# Option: actionban
|
# Option: actionban
|
||||||
# Notes.: command executed when banning an IP. Take care that the
|
# 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
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionban = iptables -I fail2ban-<name> 1 -s <ip> -j <blocktype>
|
actionban = iptables -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||||
|
|
||||||
# Option: actionunban
|
# Option: actionunban
|
||||||
# Notes.: command executed when unbanning an IP. Take care that the
|
# Notes.: command executed when unbanning an IP. Take care that the
|
||||||
|
@ -46,7 +46,7 @@ actionban = iptables -I fail2ban-<name> 1 -s <ip> -j <blocktype>
|
||||||
# Tags: See jail.conf(5) man page
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionunban = iptables -D fail2ban-<name> -s <ip> -j <blocktype>
|
actionunban = iptables -D f2b-<name> -s <ip> -j <blocktype>
|
||||||
|
|
||||||
[Init]
|
[Init]
|
||||||
|
|
||||||
|
|
|
@ -17,23 +17,23 @@ before = iptables-blocktype.conf
|
||||||
# Notes.: command executed once at the start of Fail2Ban.
|
# Notes.: command executed once at the start of Fail2Ban.
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstart = iptables -N fail2ban-<name>
|
actionstart = iptables -N f2b-<name>
|
||||||
iptables -A fail2ban-<name> -j RETURN
|
iptables -A f2b-<name> -j RETURN
|
||||||
iptables -I <chain> -m state --state NEW -p <protocol> --dport <port> -j fail2ban-<name>
|
iptables -I <chain> -m state --state NEW -p <protocol> --dport <port> -j f2b-<name>
|
||||||
|
|
||||||
# Option: actionstop
|
# Option: actionstop
|
||||||
# Notes.: command executed once at the end of Fail2Ban
|
# Notes.: command executed once at the end of Fail2Ban
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstop = iptables -D <chain> -m state --state NEW -p <protocol> --dport <port> -j fail2ban-<name>
|
actionstop = iptables -D <chain> -m state --state NEW -p <protocol> --dport <port> -j f2b-<name>
|
||||||
iptables -F fail2ban-<name>
|
iptables -F f2b-<name>
|
||||||
iptables -X fail2ban-<name>
|
iptables -X f2b-<name>
|
||||||
|
|
||||||
# Option: actioncheck
|
# Option: actioncheck
|
||||||
# Notes.: command executed once before each actionban command
|
# Notes.: command executed once before each actionban command
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actioncheck = iptables -n -L <chain> | grep -q 'fail2ban-<name>[ \t]'
|
actioncheck = iptables -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||||
|
|
||||||
# Option: actionban
|
# Option: actionban
|
||||||
# Notes.: command executed when banning an IP. Take care that the
|
# 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
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionban = iptables -I fail2ban-<name> 1 -s <ip> -j <blocktype>
|
actionban = iptables -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||||
|
|
||||||
# Option: actionunban
|
# Option: actionunban
|
||||||
# Notes.: command executed when unbanning an IP. Take care that the
|
# Notes.: command executed when unbanning an IP. Take care that the
|
||||||
|
@ -49,7 +49,7 @@ actionban = iptables -I fail2ban-<name> 1 -s <ip> -j <blocktype>
|
||||||
# Tags: See jail.conf(5) man page
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionunban = iptables -D fail2ban-<name> -s <ip> -j <blocktype>
|
actionunban = iptables -D f2b-<name> -s <ip> -j <blocktype>
|
||||||
|
|
||||||
[Init]
|
[Init]
|
||||||
|
|
||||||
|
|
|
@ -23,29 +23,29 @@ before = iptables-blocktype.conf
|
||||||
# iptables-persistent package).
|
# iptables-persistent package).
|
||||||
#
|
#
|
||||||
# Explanation of the rule below:
|
# 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
|
# 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
|
# timestamp for this IP and drop the packet. If not, let the packet
|
||||||
# through.
|
# 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
|
# and removes them from the list after some time, according to its
|
||||||
# own rules. The 3600 second timeout is independent and acts as a
|
# own rules. The 3600 second timeout is independent and acts as a
|
||||||
# safeguard in case the fail2ban process dies unexpectedly. The
|
# safeguard in case the fail2ban process dies unexpectedly. The
|
||||||
# shorter of the two timeouts actually matters.
|
# 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
|
# Option: actionstop
|
||||||
# Notes.: command executed once at the end of Fail2Ban
|
# Notes.: command executed once at the end of Fail2Ban
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstop = echo / > /proc/net/xt_recent/fail2ban-<name>
|
actionstop = echo / > /proc/net/xt_recent/f2b-<name>
|
||||||
|
|
||||||
# Option: actioncheck
|
# Option: actioncheck
|
||||||
# Notes.: command executed once before each actionban command
|
# Notes.: command executed once before each actionban command
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actioncheck = test -e /proc/net/xt_recent/fail2ban-<name>
|
actioncheck = test -e /proc/net/xt_recent/f2b-<name>
|
||||||
|
|
||||||
# Option: actionban
|
# Option: actionban
|
||||||
# Notes.: command executed when banning an IP. Take care that the
|
# 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
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionban = echo +<ip> > /proc/net/xt_recent/fail2ban-<name>
|
actionban = echo +<ip> > /proc/net/xt_recent/f2b-<name>
|
||||||
|
|
||||||
# Option: actionunban
|
# Option: actionunban
|
||||||
# Notes.: command executed when unbanning an IP. Take care that the
|
# Notes.: command executed when unbanning an IP. Take care that the
|
||||||
|
@ -61,7 +61,7 @@ actionban = echo +<ip> > /proc/net/xt_recent/fail2ban-<name>
|
||||||
# Tags: See jail.conf(5) man page
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionunban = echo -<ip> > /proc/net/xt_recent/fail2ban-<name>
|
actionunban = echo -<ip> > /proc/net/xt_recent/f2b-<name>
|
||||||
|
|
||||||
[Init]
|
[Init]
|
||||||
|
|
||||||
|
|
|
@ -14,23 +14,23 @@ before = iptables-blocktype.conf
|
||||||
# Notes.: command executed once at the start of Fail2Ban.
|
# Notes.: command executed once at the start of Fail2Ban.
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstart = iptables -N fail2ban-<name>
|
actionstart = iptables -N f2b-<name>
|
||||||
iptables -A fail2ban-<name> -j RETURN
|
iptables -A f2b-<name> -j RETURN
|
||||||
iptables -I <chain> -p <protocol> --dport <port> -j fail2ban-<name>
|
iptables -I <chain> -p <protocol> --dport <port> -j f2b-<name>
|
||||||
|
|
||||||
# Option: actionstop
|
# Option: actionstop
|
||||||
# Notes.: command executed once at the end of Fail2Ban
|
# Notes.: command executed once at the end of Fail2Ban
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionstop = iptables -D <chain> -p <protocol> --dport <port> -j fail2ban-<name>
|
actionstop = iptables -D <chain> -p <protocol> --dport <port> -j f2b-<name>
|
||||||
iptables -F fail2ban-<name>
|
iptables -F f2b-<name>
|
||||||
iptables -X fail2ban-<name>
|
iptables -X f2b-<name>
|
||||||
|
|
||||||
# Option: actioncheck
|
# Option: actioncheck
|
||||||
# Notes.: command executed once before each actionban command
|
# Notes.: command executed once before each actionban command
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actioncheck = iptables -n -L <chain> | grep -q 'fail2ban-<name>[ \t]'
|
actioncheck = iptables -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||||
|
|
||||||
# Option: actionban
|
# Option: actionban
|
||||||
# Notes.: command executed when banning an IP. Take care that the
|
# 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
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionban = iptables -I fail2ban-<name> 1 -s <ip> -j <blocktype>
|
actionban = iptables -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||||
|
|
||||||
# Option: actionunban
|
# Option: actionunban
|
||||||
# Notes.: command executed when unbanning an IP. Take care that the
|
# Notes.: command executed when unbanning an IP. Take care that the
|
||||||
|
@ -46,7 +46,7 @@ actionban = iptables -I fail2ban-<name> 1 -s <ip> -j <blocktype>
|
||||||
# Tags: See jail.conf(5) man page
|
# Tags: See jail.conf(5) man page
|
||||||
# Values: CMD
|
# Values: CMD
|
||||||
#
|
#
|
||||||
actionunban = iptables -D fail2ban-<name> -s <ip> -j <blocktype>
|
actionunban = iptables -D f2b-<name> -s <ip> -j <blocktype>
|
||||||
|
|
||||||
[Init]
|
[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:
|
# 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]
|
[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 %(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+ %(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: 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 =
|
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
|
logpath = /var/log/roundcube/userlogins
|
||||||
|
|
||||||
|
|
||||||
|
[openwebmail]
|
||||||
|
|
||||||
|
port = http,https
|
||||||
|
logpath = /var/log/openwebmail.log`
|
||||||
|
|
||||||
[sogo-auth]
|
[sogo-auth]
|
||||||
# Monitor SOGo groupware server
|
# Monitor SOGo groupware server
|
||||||
# without proxy this would be:
|
# without proxy this would be:
|
||||||
|
|
|
@ -25,7 +25,8 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import logging, os
|
import logging, os
|
||||||
from configreader import ConfigReader, DefinitionInitConfigReader
|
|
||||||
|
from .configreader import ConfigReader, DefinitionInitConfigReader
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
@ -58,22 +59,20 @@ class ActionReader(DefinitionInitConfigReader):
|
||||||
head = ["set", self._jailName]
|
head = ["set", self._jailName]
|
||||||
stream = list()
|
stream = list()
|
||||||
stream.append(head + ["addaction", self._name])
|
stream.append(head + ["addaction", self._name])
|
||||||
|
head.extend(["action", self._name])
|
||||||
for opt in self._opts:
|
for opt in self._opts:
|
||||||
if opt == "actionstart":
|
if opt == "actionstart":
|
||||||
stream.append(head + ["actionstart", self._name, self._opts[opt]])
|
stream.append(head + ["actionstart", self._opts[opt]])
|
||||||
elif opt == "actionstop":
|
elif opt == "actionstop":
|
||||||
stream.append(head + ["actionstop", self._name, self._opts[opt]])
|
stream.append(head + ["actionstop", self._opts[opt]])
|
||||||
elif opt == "actioncheck":
|
elif opt == "actioncheck":
|
||||||
stream.append(head + ["actioncheck", self._name, self._opts[opt]])
|
stream.append(head + ["actioncheck", self._opts[opt]])
|
||||||
elif opt == "actionban":
|
elif opt == "actionban":
|
||||||
stream.append(head + ["actionban", self._name, self._opts[opt]])
|
stream.append(head + ["actionban", self._opts[opt]])
|
||||||
elif opt == "actionunban":
|
elif opt == "actionunban":
|
||||||
stream.append(head + ["actionunban", self._name, self._opts[opt]])
|
stream.append(head + ["actionunban", self._opts[opt]])
|
||||||
if self._initOpts:
|
if self._initOpts:
|
||||||
if "timeout" in self._initOpts:
|
|
||||||
stream.append(head + ["timeout", self._name, self._opts["timeout"]])
|
|
||||||
# cInfo
|
|
||||||
for p in self._initOpts:
|
for p in self._initOpts:
|
||||||
stream.append(head + ["setcinfo", self._name, p, self._initOpts[p]])
|
stream.append(head + [p, self._initOpts[p]])
|
||||||
|
|
||||||
return stream
|
return stream
|
||||||
|
|
|
@ -23,7 +23,7 @@ __license__ = "GPL"
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from fail2ban.exceptions import UnknownJailException, DuplicateJailException
|
from ..exceptions import UnknownJailException, DuplicateJailException
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
@ -165,7 +165,23 @@ class Beautifier:
|
||||||
msg = "No actions for jail %s" % inC[1]
|
msg = "No actions for jail %s" % inC[1]
|
||||||
else:
|
else:
|
||||||
msg = "The jail %s has the following actions:\n" % inC[1]
|
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:
|
except Exception:
|
||||||
logSys.warning("Beautifier error. Please report the error")
|
logSys.warning("Beautifier error. Please report the error")
|
||||||
logSys.error("Beautify " + `response` + " with " + `self.__inputCmd` +
|
logSys.error("Beautify " + `response` + " with " + `self.__inputCmd` +
|
||||||
|
|
|
@ -25,9 +25,10 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import glob, logging, os
|
import glob, logging, os
|
||||||
from configparserinc import SafeConfigParserWithIncludes
|
|
||||||
from ConfigParser import NoOptionError, NoSectionError
|
from ConfigParser import NoOptionError, NoSectionError
|
||||||
|
|
||||||
|
from .configparserinc import SafeConfigParserWithIncludes
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -157,6 +158,10 @@ class DefinitionInitConfigReader(ConfigReader):
|
||||||
|
|
||||||
def read(self):
|
def read(self):
|
||||||
return ConfigReader.read(self, self._file)
|
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):
|
def getOptions(self, pOpts):
|
||||||
self._opts = ConfigReader.getOptions(
|
self._opts = ConfigReader.getOptions(
|
||||||
|
|
|
@ -25,9 +25,10 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from configreader import ConfigReader
|
|
||||||
from fail2banreader import Fail2banReader
|
from .configreader import ConfigReader
|
||||||
from jailsreader import JailsReader
|
from .fail2banreader import Fail2banReader
|
||||||
|
from .jailsreader import JailsReader
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
|
@ -25,7 +25,8 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from configreader import ConfigReader
|
|
||||||
|
from .configreader import ConfigReader
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
|
@ -25,8 +25,9 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import logging, os, shlex
|
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.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
@ -44,7 +45,7 @@ class FilterReader(DefinitionInitConfigReader):
|
||||||
def convert(self):
|
def convert(self):
|
||||||
stream = list()
|
stream = list()
|
||||||
combinedopts = dict(list(self._opts.items()) + list(self._initOpts.items()))
|
combinedopts = dict(list(self._opts.items()) + list(self._initOpts.items()))
|
||||||
opts = Action.substituteRecursiveTags(combinedopts)
|
opts = CommandAction.substituteRecursiveTags(combinedopts)
|
||||||
if not opts:
|
if not opts:
|
||||||
raise ValueError('recursive tag definitions unable to be resolved')
|
raise ValueError('recursive tag definitions unable to be resolved')
|
||||||
for opt, value in opts.iteritems():
|
for opt, value in opts.iteritems():
|
||||||
|
|
|
@ -25,10 +25,11 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import logging, re, glob, os.path
|
import logging, re, glob, os.path
|
||||||
|
import json
|
||||||
|
|
||||||
from configreader import ConfigReader
|
from .configreader import ConfigReader
|
||||||
from filterreader import FilterReader
|
from .filterreader import FilterReader
|
||||||
from actionreader import ActionReader
|
from .actionreader import ActionReader
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
@ -120,14 +121,26 @@ class JailReader(ConfigReader):
|
||||||
if not act: # skip empty actions
|
if not act: # skip empty actions
|
||||||
continue
|
continue
|
||||||
actName, actOpt = JailReader.extractOptions(act)
|
actName, actOpt = JailReader.extractOptions(act)
|
||||||
action = ActionReader(
|
if actName.endswith(".py"):
|
||||||
actName, self.__name, actOpt, basedir=self.getBaseDir())
|
self.__actions.append([
|
||||||
ret = action.read()
|
"set",
|
||||||
if ret:
|
self.__name,
|
||||||
action.getOptions(self.__opts)
|
"addaction",
|
||||||
self.__actions.append(action)
|
actOpt.pop("actname", os.path.splitext(actName)[0]),
|
||||||
|
os.path.join(
|
||||||
|
self.getBaseDir(), "action.d", actName),
|
||||||
|
json.dumps(actOpt),
|
||||||
|
])
|
||||||
else:
|
else:
|
||||||
raise AttributeError("Unable to read action")
|
action = ActionReader(
|
||||||
|
actName, self.__name, actOpt,
|
||||||
|
basedir=self.getBaseDir())
|
||||||
|
ret = action.read()
|
||||||
|
if ret:
|
||||||
|
action.getOptions(self.__opts)
|
||||||
|
self.__actions.append(action)
|
||||||
|
else:
|
||||||
|
raise AttributeError("Unable to read action")
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logSys.error("Error in action definition " + act)
|
logSys.error("Error in action definition " + act)
|
||||||
logSys.debug("Caught exception: %s" % (e,))
|
logSys.debug("Caught exception: %s" % (e,))
|
||||||
|
@ -153,7 +166,7 @@ class JailReader(ConfigReader):
|
||||||
found_files = 0
|
found_files = 0
|
||||||
for path in self.__opts[opt].split("\n"):
|
for path in self.__opts[opt].split("\n"):
|
||||||
path = path.rsplit(" ", 1)
|
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)
|
pathList = JailReader._glob(path)
|
||||||
if len(pathList) == 0:
|
if len(pathList) == 0:
|
||||||
logSys.error("No file(s) found for glob %s" % path)
|
logSys.error("No file(s) found for glob %s" % path)
|
||||||
|
@ -193,7 +206,10 @@ class JailReader(ConfigReader):
|
||||||
if self.__filter:
|
if self.__filter:
|
||||||
stream.extend(self.__filter.convert())
|
stream.extend(self.__filter.convert())
|
||||||
for action in self.__actions:
|
for action in self.__actions:
|
||||||
stream.extend(action.convert())
|
if isinstance(action, ConfigReader):
|
||||||
|
stream.extend(action.convert())
|
||||||
|
else:
|
||||||
|
stream.append(action)
|
||||||
stream.insert(0, ["add", self.__name, backend])
|
stream.insert(0, ["add", self.__name, backend])
|
||||||
return stream
|
return stream
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,9 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from configreader import ConfigReader
|
|
||||||
from jailreader import JailReader
|
from .configreader import ConfigReader
|
||||||
|
from .jailreader import JailReader
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
|
@ -29,7 +29,7 @@ __license__ = "GPL"
|
||||||
class DuplicateJailException(Exception):
|
class DuplicateJailException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class UnknownJailException(Exception):
|
class UnknownJailException(KeyError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -76,19 +76,21 @@ protocol = [
|
||||||
["set <JAIL> unbanip <IP>", "manually Unban <IP> in <JAIL>"],
|
["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> 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> 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> 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 <NAME> from <JAIL>"],
|
["set <JAIL> delaction <ACT>", "removes the action <ACT> from <JAIL>"],
|
||||||
["set <JAIL> setcinfo <ACT> <KEY> <VALUE>", "sets <VALUE> for <KEY> of the action <NAME> for <JAIL>"],
|
["", "COMMAND ACTION CONFIGURATION", ""],
|
||||||
["set <JAIL> delcinfo <ACT> <KEY>", "removes <KEY> for the action <NAME> for <JAIL>"],
|
["set <JAIL> action <ACT> actionstart <CMD>", "sets the start command <CMD> of the action <ACT> for <JAIL>"],
|
||||||
["set <JAIL> timeout <ACT> <TIMEOUT>", "sets <TIMEOUT> as the command timeout in seconds for the action <ACT> for <JAIL>"],
|
["set <JAIL> action <ACT> actionstop <CMD>", "sets the stop command <CMD> of the action <ACT> for <JAIL>"],
|
||||||
["set <JAIL> actionstart <ACT> <CMD>", "sets the start 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> actionstop <ACT> <CMD>", "sets the stop 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> actioncheck <ACT> <CMD>", "sets the check 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> actionban <ACT> <CMD>", "sets the ban 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>"],
|
||||||
["set <JAIL> actionunban <ACT> <CMD>", "sets the unban command <CMD> of 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", ""],
|
['', "JAIL INFORMATION", ""],
|
||||||
["get <JAIL> logpath", "gets the list of the monitored files for <JAIL>"],
|
["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> journalmatch", "gets the journal filter match for <JAIL>"],
|
||||||
["get <JAIL> ignoreip", "gets the list of ignored IP addresses for <JAIL>"],
|
["get <JAIL> ignoreip", "gets the list of ignored IP addresses for <JAIL>"],
|
||||||
["get <JAIL> ignorecommand", "gets ignorecommand of <JAIL>"],
|
["get <JAIL> ignorecommand", "gets ignorecommand of <JAIL>"],
|
||||||
|
@ -100,15 +102,18 @@ protocol = [
|
||||||
["get <JAIL> usedns", "gets the usedns setting for <JAIL>"],
|
["get <JAIL> usedns", "gets the usedns setting for <JAIL>"],
|
||||||
["get <JAIL> maxretry", "gets the number of failures allowed 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> 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> actions", "gets a list of actions for <JAIL>"],
|
||||||
["get <JAIL> actionstart <ACT>", "gets the start command for the action <ACT> for <JAIL>"],
|
["", "COMMAND ACTION INFORMATION",""],
|
||||||
["get <JAIL> actionstop <ACT>", "gets the stop command for the action <ACT> for <JAIL>"],
|
["get <JAIL> action <ACT> actionstart", "gets the start command for the action <ACT> for <JAIL>"],
|
||||||
["get <JAIL> actioncheck <ACT>", "gets the check command for the action <ACT> for <JAIL>"],
|
["get <JAIL> action <ACT> actionstop", "gets the stop command for the action <ACT> for <JAIL>"],
|
||||||
["get <JAIL> actionban <ACT>", "gets the ban command for the action <ACT> for <JAIL>"],
|
["get <JAIL> action <ACT> actioncheck", "gets the check command for the action <ACT> for <JAIL>"],
|
||||||
["get <JAIL> actionunban <ACT>", "gets the unban command for the action <ACT> for <JAIL>"],
|
["get <JAIL> action <ACT> actionban", "gets the ban 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> action <ACT> actionunban", "gets the unban command for the action <ACT> for <JAIL>"],
|
||||||
["get <JAIL> timeout <ACT>", "gets the command timeout in seconds 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
|
print
|
||||||
firstHeading = True
|
firstHeading = True
|
||||||
first = 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:
|
if first:
|
||||||
line = ' ' * INDENT + m[0] + ' ' * (MARGIN - len(m[0])) + n
|
line = ' ' * INDENT + m[0] + ' ' * (MARGIN - len(m[0])) + n.strip()
|
||||||
first = False
|
first = False
|
||||||
else:
|
else:
|
||||||
line = ' ' * (INDENT + MARGIN) + n
|
line = ' ' * (INDENT + MARGIN) + n.strip()
|
||||||
print line
|
print line
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -23,6 +23,8 @@ __license__ = "GPL"
|
||||||
|
|
||||||
import logging, os, subprocess, time, signal, tempfile
|
import logging, os, subprocess, time, signal, tempfile
|
||||||
import threading, re
|
import threading, re
|
||||||
|
from abc import ABCMeta
|
||||||
|
from collections import MutableMapping
|
||||||
#from subprocess import call
|
#from subprocess import call
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
|
@ -34,7 +36,7 @@ _cmd_lock = threading.Lock()
|
||||||
# Some hints on common abnormal exit codes
|
# Some hints on common abnormal exit codes
|
||||||
_RETCODE_HINTS = {
|
_RETCODE_HINTS = {
|
||||||
127: '"Command not found". Make sure that all commands in %(realCmd)r '
|
127: '"Command not found". Make sure that all commands in %(realCmd)r '
|
||||||
'are in the PATH of fail2ban-server process '
|
'are in the PATH of fail2ban-server process '
|
||||||
'(grep -a PATH= /proc/`pidof -x fail2ban-server`/environ). '
|
'(grep -a PATH= /proc/`pidof -x fail2ban-server`/environ). '
|
||||||
'You may want to start '
|
'You may want to start '
|
||||||
'"fail2ban-server -f" separately, initiate it with '
|
'"fail2ban-server -f" separately, initiate it with '
|
||||||
|
@ -46,232 +48,326 @@ _RETCODE_HINTS = {
|
||||||
signame = dict((num, name)
|
signame = dict((num, name)
|
||||||
for name, num in signal.__dict__.iteritems() if name.startswith("SIG"))
|
for name, num in signal.__dict__.iteritems() if name.startswith("SIG"))
|
||||||
|
|
||||||
##
|
class CallingMap(MutableMapping):
|
||||||
# Execute commands.
|
"""A Mapping type which returns the result of callable values.
|
||||||
#
|
|
||||||
# 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 Action:
|
`CallingMap` behaves similar to a standard python dictionary,
|
||||||
|
with the exception that any values which are callable, are called
|
||||||
def __init__(self, name):
|
and the result is returned as the value.
|
||||||
self.__name = name
|
No error handling is in place, such that any errors raised in the
|
||||||
self.__timeout = 60
|
callable will raised as usual.
|
||||||
self.__cInfo = dict()
|
Actual dictionary is stored in property `data`, and can be accessed
|
||||||
## Command executed in order to initialize the system.
|
to obtain original callable values.
|
||||||
self.__actionStart = ''
|
|
||||||
## Command executed when an IP address gets banned.
|
Attributes
|
||||||
self.__actionBan = ''
|
----------
|
||||||
## Command executed when an IP address gets removed.
|
data : dict
|
||||||
self.__actionUnban = ''
|
The dictionary data which can be accessed to obtain items
|
||||||
## Command executed in order to check requirements.
|
without callable values being called.
|
||||||
self.__actionCheck = ''
|
|
||||||
## Command executed in order to stop the system.
|
"""
|
||||||
self.__actionStop = ''
|
|
||||||
logSys.debug("Created Action")
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.data = dict(*args, **kwargs)
|
||||||
##
|
|
||||||
# Sets the action name.
|
def __getitem__(self, key):
|
||||||
#
|
value = self.data[key]
|
||||||
# @param name the name of the action
|
if callable(value):
|
||||||
|
return value()
|
||||||
def setName(self, name):
|
else:
|
||||||
self.__name = name
|
return value
|
||||||
|
|
||||||
##
|
def __setitem__(self, key, value):
|
||||||
# Returns the action name.
|
self.data[key] = value
|
||||||
#
|
|
||||||
# @return the name of the action
|
def __delitem__(self, key):
|
||||||
|
del self.data[key]
|
||||||
def getName(self):
|
|
||||||
return self.__name
|
def __iter__(self):
|
||||||
|
return iter(self.data)
|
||||||
##
|
|
||||||
# Sets the timeout period for commands.
|
def __len__(self):
|
||||||
#
|
return len(self.data)
|
||||||
# @param timeout timeout period in seconds
|
|
||||||
|
class ActionBase(object):
|
||||||
def setTimeout(self, timeout):
|
"""An abstract base class for actions in Fail2Ban.
|
||||||
self.__timeout = int(timeout)
|
|
||||||
logSys.debug("Set action %s timeout = %i" % (self.__name, timeout))
|
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.
|
||||||
# Returns the action timeout period for commands.
|
Required methods:
|
||||||
#
|
- __init__(jail, name)
|
||||||
# @return the timeout period in seconds
|
- start()
|
||||||
|
- stop()
|
||||||
def getTimeout(self):
|
- ban(aInfo)
|
||||||
return self.__timeout
|
- unban(aInfo)
|
||||||
|
"""
|
||||||
##
|
__metaclass__ = ABCMeta
|
||||||
# Sets a "CInfo".
|
|
||||||
#
|
@classmethod
|
||||||
# CInfo are statically defined properties. They can be definied by
|
def __subclasshook__(cls, C):
|
||||||
# the user and are used to set e-mail addresses, port, host or
|
required = (
|
||||||
# anything that should not change during the life of the server.
|
"start",
|
||||||
#
|
"stop",
|
||||||
# @param key the property name
|
"ban",
|
||||||
# @param value the property value
|
"unban",
|
||||||
|
)
|
||||||
def setCInfo(self, key, value):
|
for method in required:
|
||||||
self.__cInfo[key] = value
|
if not callable(getattr(C, method, None)):
|
||||||
|
|
||||||
##
|
|
||||||
# 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")
|
|
||||||
return False
|
return False
|
||||||
startCmd = Action.replaceTag(self.__actionStart, self.__cInfo)
|
return True
|
||||||
return Action.executeCmd(startCmd, self.__timeout)
|
|
||||||
|
|
||||||
##
|
|
||||||
# Set the "ban" command.
|
|
||||||
#
|
|
||||||
# @param value the command
|
|
||||||
|
|
||||||
def setActionBan(self, value):
|
|
||||||
self.__actionBan = value
|
|
||||||
logSys.debug("Set actionBan = %s" % value)
|
|
||||||
|
|
||||||
##
|
|
||||||
# Get the "ban" command.
|
|
||||||
#
|
|
||||||
# @return the command
|
|
||||||
|
|
||||||
def getActionBan(self):
|
|
||||||
return self.__actionBan
|
|
||||||
|
|
||||||
##
|
|
||||||
# Executes the action "ban" command.
|
|
||||||
#
|
|
||||||
# @return True if the command succeeded
|
|
||||||
|
|
||||||
def execActionBan(self, aInfo):
|
|
||||||
return self.__processCmd(self.__actionBan, aInfo)
|
|
||||||
|
|
||||||
##
|
|
||||||
# Set the "unban" command.
|
|
||||||
#
|
|
||||||
# @param value the command
|
|
||||||
|
|
||||||
def setActionUnban(self, value):
|
|
||||||
self.__actionUnban = value
|
|
||||||
logSys.debug("Set actionUnban = %s" % value)
|
|
||||||
|
|
||||||
##
|
|
||||||
# Get the "unban" command.
|
|
||||||
#
|
|
||||||
# @return the command
|
|
||||||
|
|
||||||
def getActionUnban(self):
|
|
||||||
return self.__actionUnban
|
|
||||||
|
|
||||||
##
|
|
||||||
# Executes the action "unban" command.
|
|
||||||
#
|
|
||||||
# @return True if the command succeeded
|
|
||||||
|
|
||||||
def execActionUnban(self, aInfo):
|
|
||||||
return self.__processCmd(self.__actionUnban, aInfo)
|
|
||||||
|
|
||||||
##
|
|
||||||
# Set the "check" command.
|
|
||||||
#
|
|
||||||
# @param value the command
|
|
||||||
|
|
||||||
def setActionCheck(self, value):
|
|
||||||
self.__actionCheck = value
|
|
||||||
logSys.debug("Set actionCheck = %s" % value)
|
|
||||||
|
|
||||||
##
|
|
||||||
# Get the "check" command.
|
|
||||||
#
|
|
||||||
# @return the command
|
|
||||||
|
|
||||||
def getActionCheck(self):
|
|
||||||
return self.__actionCheck
|
|
||||||
|
|
||||||
##
|
|
||||||
# Set the "stop" command.
|
|
||||||
#
|
|
||||||
# @param value the command
|
|
||||||
|
|
||||||
def setActionStop(self, value):
|
|
||||||
self.__actionStop = value
|
|
||||||
logSys.debug("Set actionStop = %s" % value)
|
|
||||||
|
|
||||||
##
|
|
||||||
# Get the "stop" command.
|
|
||||||
#
|
|
||||||
# @return the command
|
|
||||||
|
|
||||||
def getActionStop(self):
|
|
||||||
return self.__actionStop
|
|
||||||
|
|
||||||
##
|
|
||||||
# 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
|
|
||||||
|
|
||||||
def execActionStop(self):
|
|
||||||
stopCmd = Action.replaceTag(self.__actionStop, self.__cInfo)
|
|
||||||
return Action.executeCmd(stopCmd, self.__timeout)
|
|
||||||
|
|
||||||
##
|
def __init__(self, jail, name):
|
||||||
# Sort out tag definitions within other tags
|
"""Initialise action.
|
||||||
#
|
|
||||||
# so: becomes:
|
Called when action is created, but before the jail/actions is
|
||||||
# a = 3 a = 3
|
started. This should carry out necessary methods to initialise
|
||||||
# b = <a>_3 b = 3_3
|
the action but not "start" the action.
|
||||||
# @param tags, a dictionary
|
|
||||||
# @returns tags altered or False if there is a recursive definition
|
Parameters
|
||||||
#@staticmethod
|
----------
|
||||||
|
jail : Jail
|
||||||
|
The jail in which the action belongs to.
|
||||||
|
name : str
|
||||||
|
Name assigned to the action.
|
||||||
|
|
||||||
|
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__))
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""Executed when the jail/action is started.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""Executed when the jail/action is stopped.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def ban(self, aInfo):
|
||||||
|
"""Executed when a ban occurs.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
aInfo : dict
|
||||||
|
Dictionary which includes information in relation to
|
||||||
|
the ban.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def unban(self, aInfo):
|
||||||
|
"""Executed when a ban expires.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
aInfo : dict
|
||||||
|
Dictionary which includes information in relation to
|
||||||
|
the ban.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class CommandAction(ActionBase):
|
||||||
|
"""A action which executes OS shell commands.
|
||||||
|
|
||||||
|
This is the default type of action which Fail2Ban uses.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, jail, name):
|
||||||
|
"""Initialise action.
|
||||||
|
|
||||||
|
Default sets all commands for actions as empty string, such
|
||||||
|
no command is executed.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
jail : Jail
|
||||||
|
The jail in which the action belongs to.
|
||||||
|
name : str
|
||||||
|
Name assigned to the action.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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__)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __subclasshook__(cls, C):
|
||||||
|
return NotImplemented # Standard checks
|
||||||
|
|
||||||
|
@property
|
||||||
|
def timeout(self):
|
||||||
|
"""Time out period in seconds for execution of commands.
|
||||||
|
"""
|
||||||
|
return self._timeout
|
||||||
|
|
||||||
|
@timeout.setter
|
||||||
|
def timeout(self, timeout):
|
||||||
|
self._timeout = int(timeout)
|
||||||
|
self._logSys.debug("Set action %s timeout = %i" %
|
||||||
|
(self._name, self.timeout))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _properties(self):
|
||||||
|
"""A dictionary of the actions properties.
|
||||||
|
|
||||||
|
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)))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def actionstart(self):
|
||||||
|
"""The command executed on start of the jail/action.
|
||||||
|
"""
|
||||||
|
return self._actionstart
|
||||||
|
|
||||||
|
@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):
|
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'<([^ >]+)>')
|
t = re.compile(r'<([^ >]+)>')
|
||||||
for tag, value in tags.iteritems():
|
for tag, value in tags.iteritems():
|
||||||
value = str(value)
|
value = str(value)
|
||||||
|
@ -299,98 +395,130 @@ class Action:
|
||||||
#logSys.log(5, 'TAG: %s, newvalue: %s' % (tag, value))
|
#logSys.log(5, 'TAG: %s, newvalue: %s' % (tag, value))
|
||||||
tags[tag] = value
|
tags[tag] = value
|
||||||
return tags
|
return tags
|
||||||
substituteRecursiveTags = staticmethod(substituteRecursiveTags)
|
|
||||||
|
|
||||||
#@staticmethod
|
@staticmethod
|
||||||
def escapeTag(tag):
|
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 '\\#&;`|*?~<>^()[]{}$\'"':
|
for c in '\\#&;`|*?~<>^()[]{}$\'"':
|
||||||
if c in tag:
|
if c in value:
|
||||||
tag = tag.replace(c, '\\' + c)
|
value = value.replace(c, '\\' + c)
|
||||||
return tag
|
return value
|
||||||
escapeTag = staticmethod(escapeTag)
|
|
||||||
|
|
||||||
##
|
@classmethod
|
||||||
# Replaces tags in query with property values in aInfo.
|
def replaceTag(cls, query, aInfo):
|
||||||
#
|
"""Replaces tags in `query` with property values.
|
||||||
# @param query the query string with tags
|
|
||||||
# @param aInfo the properties
|
Parameters
|
||||||
# @return a string
|
----------
|
||||||
|
query : str
|
||||||
#@staticmethod
|
String with tags.
|
||||||
def replaceTag(query, aInfo):
|
aInfo : dict
|
||||||
""" Replace tags in query
|
Tags(keys) and associated values for substitution in query.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
`query` string with tags replaced.
|
||||||
"""
|
"""
|
||||||
string = query
|
string = query
|
||||||
for tag, value in aInfo.iteritems():
|
for tag in aInfo:
|
||||||
if "<%s>" % tag in query:
|
if "<%s>" % tag in query:
|
||||||
if callable(value):
|
value = str(aInfo[tag]) # assure string
|
||||||
value = value()
|
|
||||||
value = str(value) # assure string
|
|
||||||
if tag.endswith('matches'):
|
if tag.endswith('matches'):
|
||||||
# That one needs to be escaped since its content is
|
# That one needs to be escaped since its content is
|
||||||
# out of our control
|
# out of our control
|
||||||
value = Action.escapeTag(value)
|
value = cls.escapeTag(value)
|
||||||
string = string.replace('<' + tag + '>', value)
|
string = string.replace('<' + tag + '>', value)
|
||||||
# New line
|
# New line
|
||||||
string = string.replace("<br>", '\n')
|
string = string.replace("<br>", '\n')
|
||||||
return string
|
return string
|
||||||
replaceTag = staticmethod(replaceTag)
|
|
||||||
|
def _processCmd(self, cmd, aInfo = None):
|
||||||
##
|
"""Executes a command with preliminary checks and substitutions.
|
||||||
# Executes a command with preliminary checks and substitutions.
|
|
||||||
#
|
Before executing any commands, executes the "check" command first
|
||||||
# Before executing any commands, executes the "check" command first
|
in order to check if pre-requirements are met. If this check fails,
|
||||||
# in order to check if pre-requirements are met. If this check fails,
|
it tries to restore a sane environment before executing the real
|
||||||
# it tries to restore a sane environment before executing the real
|
command.
|
||||||
# command.
|
|
||||||
# Replaces "aInfo" and "cInfo" in the query too.
|
Parameters
|
||||||
#
|
----------
|
||||||
# @param cmd The command to execute
|
cmd : str
|
||||||
# @param aInfo Dynamic properties
|
The command to execute.
|
||||||
# @return True if the command succeeded
|
aInfo : dictionary
|
||||||
|
Dynamic properties.
|
||||||
def __processCmd(self, cmd, aInfo = None):
|
|
||||||
""" Executes an OS command.
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
True if the command succeeded.
|
||||||
"""
|
"""
|
||||||
if cmd == "":
|
if cmd == "":
|
||||||
logSys.debug("Nothing to do")
|
self._logSys.debug("Nothing to do")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
checkCmd = Action.replaceTag(self.__actionCheck, self.__cInfo)
|
checkCmd = self.replaceTag(self.actioncheck, self._properties)
|
||||||
if not Action.executeCmd(checkCmd, self.__timeout):
|
if not self.executeCmd(checkCmd, self.timeout):
|
||||||
logSys.error("Invariant check failed. Trying to restore a sane" +
|
self._logSys.error(
|
||||||
" environment")
|
"Invariant check failed. Trying to restore a sane environment")
|
||||||
self.execActionStop()
|
self.stop()
|
||||||
self.execActionStart()
|
self.start()
|
||||||
if not Action.executeCmd(checkCmd, self.__timeout):
|
if not self.executeCmd(checkCmd, self.timeout):
|
||||||
logSys.fatal("Unable to restore environment")
|
self._logSys.fatal("Unable to restore environment")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Replace tags
|
# Replace tags
|
||||||
if not aInfo is None:
|
if not aInfo is None:
|
||||||
realCmd = Action.replaceTag(cmd, aInfo)
|
realCmd = self.replaceTag(cmd, aInfo)
|
||||||
else:
|
else:
|
||||||
realCmd = cmd
|
realCmd = cmd
|
||||||
|
|
||||||
# Replace static fields
|
# 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)
|
||||||
|
|
||||||
##
|
@staticmethod
|
||||||
# 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
|
|
||||||
def executeCmd(realCmd, timeout=60):
|
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)
|
logSys.debug(realCmd)
|
||||||
if not realCmd:
|
if not realCmd:
|
||||||
logSys.debug("Nothing to do")
|
logSys.debug("Nothing to do")
|
||||||
|
@ -420,7 +548,6 @@ class Action:
|
||||||
retcode = popen.poll()
|
retcode = popen.poll()
|
||||||
except OSError, e:
|
except OSError, e:
|
||||||
logSys.error("%s -- failed with %s" % (realCmd, e))
|
logSys.error("%s -- failed with %s" % (realCmd, e))
|
||||||
return False
|
|
||||||
finally:
|
finally:
|
||||||
_cmd_lock.release()
|
_cmd_lock.release()
|
||||||
|
|
||||||
|
@ -447,6 +574,6 @@ class Action:
|
||||||
if msg:
|
if msg:
|
||||||
logSys.info("HINT on %i: %s"
|
logSys.info("HINT on %i: %s"
|
||||||
% (retcode, msg % locals()))
|
% (retcode, msg % locals()))
|
||||||
return False
|
return False
|
||||||
executeCmd = staticmethod(executeCmd)
|
raise RuntimeError("Command execution failed: %s" % realCmd)
|
||||||
|
|
||||||
|
|
|
@ -24,94 +24,113 @@ __author__ = "Cyril Jaquier"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
from banmanager import BanManager
|
|
||||||
from jailthread import JailThread
|
|
||||||
from action import Action
|
|
||||||
from mytime import MyTime
|
|
||||||
import time, logging
|
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.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
##
|
class Actions(JailThread, Mapping):
|
||||||
# Execute commands.
|
"""Handles jail actions.
|
||||||
#
|
|
||||||
# This class reads the failures from the Jail queue and decide if an
|
This class handles the actions of the jail. Creation, deletion or to
|
||||||
# action has to be taken. A BanManager take care of the banned IP
|
actions must be done through this class. This class is based on the
|
||||||
# addresses.
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
class Actions(JailThread):
|
|
||||||
|
|
||||||
##
|
|
||||||
# Constructor.
|
|
||||||
#
|
|
||||||
# Initialize the filter object with default values.
|
|
||||||
# @param jail the jail object
|
|
||||||
|
|
||||||
def __init__(self, jail):
|
def __init__(self, jail):
|
||||||
|
"""Initialise an empty Actions instance.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
jail: Jail
|
||||||
|
The jail of which the actions belongs to.
|
||||||
|
"""
|
||||||
JailThread.__init__(self)
|
JailThread.__init__(self)
|
||||||
## The jail which contains this action.
|
## The jail which contains this action.
|
||||||
self.jail = jail
|
self._jail = jail
|
||||||
self.__actions = list()
|
self._actions = dict()
|
||||||
## The ban manager.
|
## The ban manager.
|
||||||
self.__banManager = BanManager()
|
self.__banManager = BanManager()
|
||||||
|
|
||||||
##
|
def add(self, name, pythonModule=None, initOpts=None):
|
||||||
# Adds an action.
|
"""Adds a new action.
|
||||||
#
|
|
||||||
# @param name The action name
|
Add a new action if not already present, defaulting to standard
|
||||||
|
`CommandAction`, or specified Python module.
|
||||||
def addAction(self, name):
|
|
||||||
|
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
|
# 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)
|
raise ValueError("Action %s already exists" % name)
|
||||||
action = Action(name)
|
if pythonModule is None:
|
||||||
self.__actions.append(action)
|
action = CommandAction(self._jail, name)
|
||||||
|
else:
|
||||||
##
|
pythonModuleName = os.path.basename(pythonModule.strip(".py"))
|
||||||
# Removes an action.
|
customActionModule = imp.load_source(
|
||||||
#
|
pythonModuleName, pythonModule)
|
||||||
# @param name The action name
|
if not hasattr(customActionModule, "Action"):
|
||||||
|
raise RuntimeError(
|
||||||
def delAction(self, name):
|
"%s module does not have 'Action' class" % pythonModule)
|
||||||
for action in self.__actions:
|
elif not issubclass(customActionModule.Action, ActionBase):
|
||||||
if action.getName() == name:
|
raise RuntimeError(
|
||||||
self.__actions.remove(action)
|
"%s module %s does not implement required methods" % (
|
||||||
return
|
pythonModule, customActionModule.Action.__name__))
|
||||||
raise KeyError("Invalid Action name: %s" % name)
|
action = customActionModule.Action(self._jail, name, **initOpts)
|
||||||
|
self._actions[name] = action
|
||||||
##
|
|
||||||
# Returns an action.
|
def __getitem__(self, name):
|
||||||
#
|
try:
|
||||||
# Raises a KeyError exception if the action does not exist.
|
return self._actions[name]
|
||||||
#
|
except KeyError:
|
||||||
# @param name the action name
|
raise KeyError("Invalid Action name: %s" % name)
|
||||||
# @return the action
|
|
||||||
|
def __delitem__(self, name):
|
||||||
def getAction(self, name):
|
try:
|
||||||
for action in self.__actions:
|
del self._actions[name]
|
||||||
if action.getName() == name:
|
except KeyError:
|
||||||
return action
|
raise KeyError("Invalid Action name: %s" % name)
|
||||||
raise KeyError("Invalid Action name")
|
|
||||||
|
def __iter__(self):
|
||||||
##
|
return iter(self._actions)
|
||||||
# Returns the last defined action.
|
|
||||||
#
|
def __len__(self):
|
||||||
# @return The last defined action.
|
return len(self._actions)
|
||||||
|
|
||||||
def getLastAction(self):
|
def __eq__(self, other): # Required for Threading
|
||||||
action = self.__actions.pop()
|
return False
|
||||||
self.__actions.append(action)
|
|
||||||
return action
|
def __hash__(self): # Required for Threading
|
||||||
|
return id(self)
|
||||||
##
|
|
||||||
# Returns the list of actions
|
|
||||||
#
|
|
||||||
# @return list of actions
|
|
||||||
|
|
||||||
def getActions(self):
|
|
||||||
return self.__actions
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Set the ban time.
|
# Set the ban time.
|
||||||
#
|
#
|
||||||
|
@ -128,34 +147,52 @@ class Actions(JailThread):
|
||||||
|
|
||||||
def getBanTime(self):
|
def getBanTime(self):
|
||||||
return self.__banManager.getBanTime()
|
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):
|
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.
|
# Find the ticket with the IP.
|
||||||
ticket = self.__banManager.getTicketByIP(ip)
|
ticket = self.__banManager.getTicketByIP(ip)
|
||||||
if ticket is not None:
|
if ticket is not None:
|
||||||
# Unban the IP.
|
# Unban the IP.
|
||||||
self.__unBan(ticket)
|
self.__unBan(ticket)
|
||||||
return ip
|
else:
|
||||||
raise ValueError("IP %s is not banned" % ip)
|
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):
|
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)
|
self.setActive(True)
|
||||||
for action in self.__actions:
|
for name, action in self._actions.iteritems():
|
||||||
action.execActionStart()
|
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():
|
while self._isActive():
|
||||||
if not self.getIdle():
|
if not self.getIdle():
|
||||||
#logSys.debug(self.jail.getName() + ": action")
|
#logSys.debug(self._jail.getName() + ": action")
|
||||||
ret = self.__checkBan()
|
ret = self.__checkBan()
|
||||||
if not ret:
|
if not ret:
|
||||||
self.__checkUnBan()
|
self.__checkUnBan()
|
||||||
|
@ -163,94 +200,116 @@ class Actions(JailThread):
|
||||||
else:
|
else:
|
||||||
time.sleep(self.getSleepTime())
|
time.sleep(self.getSleepTime())
|
||||||
self.__flushBan()
|
self.__flushBan()
|
||||||
for action in self.__actions:
|
for name, action in self._actions.iteritems():
|
||||||
action.execActionStop()
|
try:
|
||||||
logSys.debug(self.jail.getName() + ": action terminated")
|
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
|
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):
|
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:
|
if ticket != False:
|
||||||
aInfo = dict()
|
aInfo = CallingMap()
|
||||||
bTicket = BanManager.createBanTicket(ticket)
|
bTicket = BanManager.createBanTicket(ticket)
|
||||||
aInfo["ip"] = bTicket.getIP()
|
aInfo["ip"] = bTicket.getIP()
|
||||||
aInfo["failures"] = bTicket.getAttempt()
|
aInfo["failures"] = bTicket.getAttempt()
|
||||||
aInfo["time"] = bTicket.getTime()
|
aInfo["time"] = bTicket.getTime()
|
||||||
aInfo["matches"] = "\n".join(bTicket.getMatches())
|
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(
|
aInfo["ipmatches"] = lambda: "\n".join(
|
||||||
self.jail.getDatabase().getBansMerged(
|
self._jail.getDatabase().getBansMerged(
|
||||||
ip=bTicket.getIP()).getMatches())
|
ip=bTicket.getIP()).getMatches())
|
||||||
aInfo["ipjailmatches"] = lambda: "\n".join(
|
aInfo["ipjailmatches"] = lambda: "\n".join(
|
||||||
self.jail.getDatabase().getBansMerged(
|
self._jail.getDatabase().getBansMerged(
|
||||||
ip=bTicket.getIP(), jail=self.jail).getMatches())
|
ip=bTicket.getIP(), jail=self._jail).getMatches())
|
||||||
aInfo["ipfailures"] = lambda: "\n".join(
|
aInfo["ipfailures"] = lambda: "\n".join(
|
||||||
self.jail.getDatabase().getBansMerged(
|
self._jail.getDatabase().getBansMerged(
|
||||||
ip=bTicket.getIP()).getAttempt())
|
ip=bTicket.getIP()).getAttempt())
|
||||||
aInfo["ipjailfailures"] = lambda: "\n".join(
|
aInfo["ipjailfailures"] = lambda: "\n".join(
|
||||||
self.jail.getDatabase().getBansMerged(
|
self._jail.getDatabase().getBansMerged(
|
||||||
ip=bTicket.getIP(), jail=self.jail).getAttempt())
|
ip=bTicket.getIP(), jail=self._jail).getAttempt())
|
||||||
if self.__banManager.addBanTicket(bTicket):
|
if self.__banManager.addBanTicket(bTicket):
|
||||||
logSys.warning("[%s] Ban %s" % (self.jail.getName(), aInfo["ip"]))
|
logSys.warning("[%s] Ban %s" % (self._jail.getName(), aInfo["ip"]))
|
||||||
for action in self.__actions:
|
for name, action in self._actions.iteritems():
|
||||||
action.execActionBan(aInfo)
|
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
|
return True
|
||||||
else:
|
else:
|
||||||
logSys.info("[%s] %s already banned" % (self.jail.getName(),
|
logSys.info("[%s] %s already banned" % (self._jail.getName(),
|
||||||
aInfo["ip"]))
|
aInfo["ip"]))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
##
|
|
||||||
# Check for IP address to unban.
|
|
||||||
#
|
|
||||||
# Unban IP address which are outdated.
|
|
||||||
|
|
||||||
def __checkUnBan(self):
|
def __checkUnBan(self):
|
||||||
|
"""Check for IP address to unban.
|
||||||
|
|
||||||
|
Unban IP addresses which are outdated.
|
||||||
|
"""
|
||||||
for ticket in self.__banManager.unBanList(MyTime.time()):
|
for ticket in self.__banManager.unBanList(MyTime.time()):
|
||||||
self.__unBan(ticket)
|
self.__unBan(ticket)
|
||||||
|
|
||||||
##
|
|
||||||
# Flush the ban list.
|
|
||||||
#
|
|
||||||
# Unban all IP address which are still in the banning list.
|
|
||||||
|
|
||||||
def __flushBan(self):
|
def __flushBan(self):
|
||||||
|
"""Flush the ban list.
|
||||||
|
|
||||||
|
Unban all IP address which are still in the banning list.
|
||||||
|
"""
|
||||||
logSys.debug("Flush ban list")
|
logSys.debug("Flush ban list")
|
||||||
for ticket in self.__banManager.flushBanList():
|
for ticket in self.__banManager.flushBanList():
|
||||||
self.__unBan(ticket)
|
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):
|
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 = dict()
|
||||||
aInfo["ip"] = ticket.getIP()
|
aInfo["ip"] = ticket.getIP()
|
||||||
aInfo["failures"] = ticket.getAttempt()
|
aInfo["failures"] = ticket.getAttempt()
|
||||||
aInfo["time"] = ticket.getTime()
|
aInfo["time"] = ticket.getTime()
|
||||||
aInfo["matches"] = "".join(ticket.getMatches())
|
aInfo["matches"] = "".join(ticket.getMatches())
|
||||||
logSys.warning("[%s] Unban %s" % (self.jail.getName(), aInfo["ip"]))
|
logSys.warning("[%s] Unban %s" % (self._jail.getName(), aInfo["ip"]))
|
||||||
for action in self.__actions:
|
for name, action in self._actions.iteritems():
|
||||||
action.execActionUnban(aInfo)
|
try:
|
||||||
|
action.unban(aInfo)
|
||||||
|
except Exception as e:
|
||||||
##
|
logSys.error(
|
||||||
# Get the status of the filter.
|
"Failed to execute unban jail '%s' action '%s': %s",
|
||||||
#
|
self._jail.getName(), name, e)
|
||||||
# Get some informations about the filter state such as the total
|
|
||||||
# number of failures.
|
|
||||||
# @return a list with tuple
|
|
||||||
|
|
||||||
def status(self):
|
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()),
|
ret = [("Currently banned", self.__banManager.size()),
|
||||||
("Total banned", self.__banManager.getBanTotal()),
|
("Total banned", self.__banManager.getBanTotal()),
|
||||||
("IP list", self.__banManager.getBanList())]
|
("IP list", self.__banManager.getBanList())]
|
||||||
|
|
|
@ -27,7 +27,7 @@ __license__ = "GPL"
|
||||||
from pickle import dumps, loads, HIGHEST_PROTOCOL
|
from pickle import dumps, loads, HIGHEST_PROTOCOL
|
||||||
import asyncore, asynchat, socket, os, logging, sys, traceback, fcntl
|
import asyncore, asynchat, socket, os, logging, sys, traceback, fcntl
|
||||||
|
|
||||||
from fail2ban import helpers
|
from .. import helpers
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
@ -76,7 +76,7 @@ class RequestHandler(asynchat.async_chat):
|
||||||
# Serializes the response.
|
# Serializes the response.
|
||||||
message = dumps(message, HIGHEST_PROTOCOL)
|
message = dumps(message, HIGHEST_PROTOCOL)
|
||||||
# Sends the response to the client.
|
# Sends the response to the client.
|
||||||
self.send(message + RequestHandler.END_STRING)
|
self.push(message + RequestHandler.END_STRING)
|
||||||
# Closes the channel.
|
# Closes the channel.
|
||||||
self.close_when_done()
|
self.close_when_done()
|
||||||
|
|
||||||
|
|
|
@ -24,10 +24,11 @@ __author__ = "Cyril Jaquier"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
from ticket import BanTicket
|
|
||||||
from threading import Lock
|
|
||||||
from mytime import MyTime
|
|
||||||
import logging
|
import logging
|
||||||
|
from threading import Lock
|
||||||
|
|
||||||
|
from .ticket import BanTicket
|
||||||
|
from .mytime import MyTime
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
|
@ -29,8 +29,8 @@ import json
|
||||||
import locale
|
import locale
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from fail2ban.server.mytime import MyTime
|
from .mytime import MyTime
|
||||||
from fail2ban.server.ticket import FailTicket
|
from .ticket import FailTicket
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
|
@ -22,10 +22,10 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import sys, time, logging
|
import sys, time, logging
|
||||||
|
|
||||||
from datetemplate import DatePatternRegex, DateTai64n, DateEpoch, DateISO8601
|
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
|
||||||
|
from .datetemplate import DatePatternRegex, DateTai64n, DateEpoch, DateISO8601
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -25,14 +25,13 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import re, time, calendar
|
import re, time, calendar
|
||||||
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from mytime import MyTime
|
from .mytime import MyTime
|
||||||
import iso8601
|
from . import iso8601
|
||||||
|
|
||||||
import logging
|
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,11 +24,12 @@ __author__ = "Cyril Jaquier"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
from faildata import FailData
|
|
||||||
from ticket import FailTicket
|
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from .faildata import FailData
|
||||||
|
from .ticket import FailTicket
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -21,18 +21,17 @@ __author__ = "Cyril Jaquier and Fail2Ban Contributors"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2013 Yaroslav Halchenko"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2013 Yaroslav Halchenko"
|
||||||
__license__ = "GPL"
|
__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
|
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.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -378,9 +377,9 @@ class Filter(JailThread):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if self.__ignoreCommand:
|
if self.__ignoreCommand:
|
||||||
command = Action.replaceTag(self.__ignoreCommand, { 'ip': ip } )
|
command = CommandAction.replaceTag(self.__ignoreCommand, { 'ip': ip } )
|
||||||
logSys.debug('ignore command: ' + command)
|
logSys.debug('ignore command: ' + command)
|
||||||
return Action.executeCmd(command)
|
return CommandAction.executeCmd(command)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
|
@ -23,11 +23,13 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2012 Yaroslav Halchenko"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2012 Yaroslav Halchenko"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
from failmanager import FailManagerEmpty
|
import time, logging, fcntl
|
||||||
from filter import FileFilter
|
|
||||||
from mytime import MyTime
|
|
||||||
|
|
||||||
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.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
|
@ -24,12 +24,12 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier; 2012 Yaroslav Halchenko"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier; 2012 Yaroslav Halchenko"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
from failmanager import FailManagerEmpty
|
|
||||||
from filter import FileFilter
|
|
||||||
from mytime import MyTime
|
|
||||||
|
|
||||||
import time, logging, os
|
import time, logging, os
|
||||||
|
|
||||||
|
from .failmanager import FailManagerEmpty
|
||||||
|
from .filter import FileFilter
|
||||||
|
from .mytime import MyTime
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -24,13 +24,12 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2012 Lee Clemens, 2012 Y
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import time, logging, pyinotify
|
import time, logging, pyinotify
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
from distutils.version import LooseVersion
|
||||||
from os.path import dirname, sep as pathsep
|
from os.path import dirname, sep as pathsep
|
||||||
|
|
||||||
from failmanager import FailManagerEmpty
|
from .failmanager import FailManagerEmpty
|
||||||
from filter import FileFilter
|
from .filter import FileFilter
|
||||||
from mytime import MyTime
|
from .mytime import MyTime
|
||||||
|
|
||||||
|
|
||||||
if not hasattr(pyinotify, '__version__') \
|
if not hasattr(pyinotify, '__version__') \
|
||||||
|
|
|
@ -29,9 +29,9 @@ from systemd import journal
|
||||||
if LooseVersion(getattr(journal, '__version__', "0")) < '204':
|
if LooseVersion(getattr(journal, '__version__', "0")) < '204':
|
||||||
raise ImportError("Fail2Ban requires systemd >= 204")
|
raise ImportError("Fail2Ban requires systemd >= 204")
|
||||||
|
|
||||||
from failmanager import FailManagerEmpty
|
from .failmanager import FailManagerEmpty
|
||||||
from filter import JournalFilter
|
from .filter import JournalFilter
|
||||||
from mytime import MyTime
|
from .mytime import MyTime
|
||||||
|
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
|
|
|
@ -25,7 +25,7 @@ __license__ = "GPL"
|
||||||
|
|
||||||
import Queue, logging
|
import Queue, logging
|
||||||
|
|
||||||
from actions import Actions
|
from .actions import Actions
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
@ -71,7 +71,7 @@ class Jail:
|
||||||
"%r was requested" % (b, backend))
|
"%r was requested" % (b, backend))
|
||||||
else:
|
else:
|
||||||
logSys.info("Initiated %r backend" % b)
|
logSys.info("Initiated %r backend" % b)
|
||||||
self.__action = Actions(self)
|
self.__actions = Actions(self)
|
||||||
return # we are done
|
return # we are done
|
||||||
except ImportError, e:
|
except ImportError, e:
|
||||||
logSys.debug(
|
logSys.debug(
|
||||||
|
@ -110,11 +110,10 @@ class Jail:
|
||||||
self.__filter = FilterSystemd(self)
|
self.__filter = FilterSystemd(self)
|
||||||
|
|
||||||
def setName(self, name):
|
def setName(self, name):
|
||||||
# 20 based on iptable chain name limit of 30 less len('fail2ban-')
|
# 26 based on iptable chain name limit of 30 less len('f2b-')
|
||||||
if len(name) >= 20:
|
if len(name) >= 26:
|
||||||
logSys.warning("Jail name %r might be too long and some commands"
|
logSys.warning("Jail name %r might be too long and some commands "
|
||||||
" (e.g. iptables) might not function correctly."
|
"might not function correctly. Please shorten"
|
||||||
" Please shorten"
|
|
||||||
% name)
|
% name)
|
||||||
self.__name = name
|
self.__name = name
|
||||||
|
|
||||||
|
@ -124,11 +123,17 @@ class Jail:
|
||||||
def getDatabase(self):
|
def getDatabase(self):
|
||||||
return self.__db
|
return self.__db
|
||||||
|
|
||||||
def getFilter(self):
|
@property
|
||||||
|
def filter(self):
|
||||||
|
"""The filter which the jail is using to monitor log files.
|
||||||
|
"""
|
||||||
return self.__filter
|
return self.__filter
|
||||||
|
|
||||||
def getAction(self):
|
@property
|
||||||
return self.__action
|
def actions(self):
|
||||||
|
"""Actions object used to manage actions for jail.
|
||||||
|
"""
|
||||||
|
return self.__actions
|
||||||
|
|
||||||
def putFailTicket(self, ticket):
|
def putFailTicket(self, ticket):
|
||||||
self.__queue.put(ticket)
|
self.__queue.put(ticket)
|
||||||
|
@ -143,36 +148,36 @@ class Jail:
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self.__filter.start()
|
self.__filter.start()
|
||||||
self.__action.start()
|
self.__actions.start()
|
||||||
# Restore any previous valid bans from the database
|
# Restore any previous valid bans from the database
|
||||||
if self.__db is not None:
|
if self.__db is not None:
|
||||||
for ticket in self.__db.getBans(
|
for ticket in self.__db.getBans(
|
||||||
jail=self, bantime=self.__action.getBanTime()):
|
jail=self, bantime=self.__actions.getBanTime()):
|
||||||
self.__queue.put(ticket)
|
self.__queue.put(ticket)
|
||||||
logSys.info("Jail '%s' started" % self.__name)
|
logSys.info("Jail '%s' started" % self.__name)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.__filter.stop()
|
self.__filter.stop()
|
||||||
self.__action.stop()
|
self.__actions.stop()
|
||||||
self.__filter.join()
|
self.__filter.join()
|
||||||
self.__action.join()
|
self.__actions.join()
|
||||||
logSys.info("Jail '%s' stopped" % self.__name)
|
logSys.info("Jail '%s' stopped" % self.__name)
|
||||||
|
|
||||||
def isAlive(self):
|
def isAlive(self):
|
||||||
isAlive0 = self.__filter.isAlive()
|
isAlive0 = self.__filter.isAlive()
|
||||||
isAlive1 = self.__action.isAlive()
|
isAlive1 = self.__actions.isAlive()
|
||||||
return isAlive0 or isAlive1
|
return isAlive0 or isAlive1
|
||||||
|
|
||||||
def setIdle(self, value):
|
def setIdle(self, value):
|
||||||
self.__filter.setIdle(value)
|
self.__filter.setIdle(value)
|
||||||
self.__action.setIdle(value)
|
self.__actions.setIdle(value)
|
||||||
|
|
||||||
def getIdle(self):
|
def getIdle(self):
|
||||||
return self.__filter.getIdle() or self.__action.getIdle()
|
return self.__filter.getIdle() or self.__actions.getIdle()
|
||||||
|
|
||||||
def getStatus(self):
|
def getStatus(self):
|
||||||
fStatus = self.__filter.status()
|
fStatus = self.__filter.status()
|
||||||
aStatus = self.__action.status()
|
aStatus = self.__actions.status()
|
||||||
ret = [("filter", fStatus),
|
ret = [("filter", fStatus),
|
||||||
("action", aStatus)]
|
("action", aStatus)]
|
||||||
return ret
|
return ret
|
||||||
|
|
|
@ -21,137 +21,84 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2013- Yaroslav Halchenko"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2013- Yaroslav Halchenko"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
from fail2ban.exceptions import DuplicateJailException, UnknownJailException
|
|
||||||
|
|
||||||
from jail import Jail
|
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
from collections import Mapping
|
||||||
|
|
||||||
##
|
from ..exceptions import DuplicateJailException, UnknownJailException
|
||||||
# Handles the jails.
|
from .jail import Jail
|
||||||
#
|
|
||||||
# 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
|
class Jails(Mapping):
|
||||||
# the jail itself, including filter and actions.
|
"""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.
|
||||||
|
"""
|
||||||
|
|
||||||
class Jails:
|
|
||||||
|
|
||||||
##
|
|
||||||
# Constructor.
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
"""Initialise an empty Jails instance.
|
||||||
|
"""
|
||||||
self.__lock = Lock()
|
self.__lock = Lock()
|
||||||
self.__jails = dict()
|
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
|
|
||||||
|
|
||||||
def add(self, name, backend, db=None):
|
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:
|
try:
|
||||||
self.__lock.acquire()
|
self.__lock.acquire()
|
||||||
if self.__jails.has_key(name):
|
if name in self._jails:
|
||||||
raise DuplicateJailException(name)
|
raise DuplicateJailException(name)
|
||||||
else:
|
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):
|
|
||||||
try:
|
|
||||||
self.__lock.acquire()
|
|
||||||
if self.__jails.has_key(name):
|
|
||||||
del self.__jails[name]
|
|
||||||
else:
|
|
||||||
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):
|
|
||||||
try:
|
|
||||||
self.__lock.acquire()
|
|
||||||
if self.__jails.has_key(name):
|
|
||||||
jail = self.__jails[name]
|
|
||||||
return jail
|
|
||||||
else:
|
|
||||||
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):
|
|
||||||
try:
|
|
||||||
self.__lock.acquire()
|
|
||||||
if self.__jails.has_key(name):
|
|
||||||
action = self.__jails[name].getAction()
|
|
||||||
return action
|
|
||||||
else:
|
|
||||||
raise UnknownJailException(name)
|
|
||||||
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):
|
|
||||||
try:
|
|
||||||
self.__lock.acquire()
|
|
||||||
if self.__jails.has_key(name):
|
|
||||||
action = self.__jails[name].getFilter()
|
|
||||||
return action
|
|
||||||
else:
|
|
||||||
raise UnknownJailException(name)
|
|
||||||
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:
|
finally:
|
||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
|
|
||||||
|
def __getitem__(self, name):
|
||||||
|
try:
|
||||||
|
self.__lock.acquire()
|
||||||
|
return self._jails[name]
|
||||||
|
except KeyError:
|
||||||
|
raise UnknownJailException(name)
|
||||||
|
finally:
|
||||||
|
self.__lock.release()
|
||||||
|
|
||||||
|
def __delitem__(self, name):
|
||||||
|
try:
|
||||||
|
self.__lock.acquire()
|
||||||
|
del self._jails[name]
|
||||||
|
except KeyError:
|
||||||
|
raise UnknownJailException(name)
|
||||||
|
finally:
|
||||||
|
self.__lock.release()
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
try:
|
||||||
|
self.__lock.acquire()
|
||||||
|
return len(self._jails)
|
||||||
|
finally:
|
||||||
|
self.__lock.release()
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
try:
|
||||||
|
self.__lock.acquire()
|
||||||
|
return iter(self._jails)
|
||||||
|
finally:
|
||||||
|
self.__lock.release()
|
||||||
|
|
|
@ -25,15 +25,16 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
from threading import Lock, RLock
|
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
|
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.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -121,10 +122,10 @@ class Server:
|
||||||
def addJail(self, name, backend):
|
def addJail(self, name, backend):
|
||||||
self.__jails.add(name, backend, self.__db)
|
self.__jails.add(name, backend, self.__db)
|
||||||
if self.__db is not None:
|
if self.__db is not None:
|
||||||
self.__db.addJail(self.__jails.get(name))
|
self.__db.addJail(self.__jails[name])
|
||||||
|
|
||||||
def delJail(self, name):
|
def delJail(self, name):
|
||||||
self.__jails.remove(name)
|
del self.__jails[name]
|
||||||
if self.__db is not None:
|
if self.__db is not None:
|
||||||
self.__db.delJailName(name)
|
self.__db.delJailName(name)
|
||||||
|
|
||||||
|
@ -132,7 +133,7 @@ class Server:
|
||||||
try:
|
try:
|
||||||
self.__lock.acquire()
|
self.__lock.acquire()
|
||||||
if not self.isAlive(name):
|
if not self.isAlive(name):
|
||||||
self.__jails.get(name).start()
|
self.__jails[name].start()
|
||||||
finally:
|
finally:
|
||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
|
|
||||||
|
@ -141,7 +142,7 @@ class Server:
|
||||||
try:
|
try:
|
||||||
self.__lock.acquire()
|
self.__lock.acquire()
|
||||||
if self.isAlive(name):
|
if self.isAlive(name):
|
||||||
self.__jails.get(name).stop()
|
self.__jails[name].stop()
|
||||||
self.delJail(name)
|
self.delJail(name)
|
||||||
finally:
|
finally:
|
||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
|
@ -150,43 +151,43 @@ class Server:
|
||||||
logSys.info("Stopping all jails")
|
logSys.info("Stopping all jails")
|
||||||
try:
|
try:
|
||||||
self.__lock.acquire()
|
self.__lock.acquire()
|
||||||
for jail in self.__jails.getAll():
|
for jail in self.__jails.keys():
|
||||||
self.stopJail(jail)
|
self.stopJail(jail)
|
||||||
finally:
|
finally:
|
||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
|
|
||||||
def isAlive(self, name):
|
def isAlive(self, name):
|
||||||
return self.__jails.get(name).isAlive()
|
return self.__jails[name].isAlive()
|
||||||
|
|
||||||
def setIdleJail(self, name, value):
|
def setIdleJail(self, name, value):
|
||||||
self.__jails.get(name).setIdle(value)
|
self.__jails[name].setIdle(value)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def getIdleJail(self, name):
|
def getIdleJail(self, name):
|
||||||
return self.__jails.get(name).getIdle()
|
return self.__jails[name].getIdle()
|
||||||
|
|
||||||
# Filter
|
# Filter
|
||||||
def addIgnoreIP(self, name, ip):
|
def addIgnoreIP(self, name, ip):
|
||||||
self.__jails.getFilter(name).addIgnoreIP(ip)
|
self.__jails[name].filter.addIgnoreIP(ip)
|
||||||
|
|
||||||
def delIgnoreIP(self, name, ip):
|
def delIgnoreIP(self, name, ip):
|
||||||
self.__jails.getFilter(name).delIgnoreIP(ip)
|
self.__jails[name].filter.delIgnoreIP(ip)
|
||||||
|
|
||||||
def getIgnoreIP(self, name):
|
def getIgnoreIP(self, name):
|
||||||
return self.__jails.getFilter(name).getIgnoreIP()
|
return self.__jails[name].filter.getIgnoreIP()
|
||||||
|
|
||||||
def addLogPath(self, name, fileName, tail=False):
|
def addLogPath(self, name, fileName, tail=False):
|
||||||
filter_ = self.__jails.getFilter(name)
|
filter_ = self.__jails[name].filter
|
||||||
if isinstance(filter_, FileFilter):
|
if isinstance(filter_, FileFilter):
|
||||||
filter_.addLogPath(fileName, tail)
|
filter_.addLogPath(fileName, tail)
|
||||||
|
|
||||||
def delLogPath(self, name, fileName):
|
def delLogPath(self, name, fileName):
|
||||||
filter_ = self.__jails.getFilter(name)
|
filter_ = self.__jails[name].filter
|
||||||
if isinstance(filter_, FileFilter):
|
if isinstance(filter_, FileFilter):
|
||||||
filter_.delLogPath(fileName)
|
filter_.delLogPath(fileName)
|
||||||
|
|
||||||
def getLogPath(self, name):
|
def getLogPath(self, name):
|
||||||
filter_ = self.__jails.getFilter(name)
|
filter_ = self.__jails[name].filter
|
||||||
if isinstance(filter_, FileFilter):
|
if isinstance(filter_, FileFilter):
|
||||||
return [m.getFileName()
|
return [m.getFileName()
|
||||||
for m in filter_.getLogPath()]
|
for m in filter_.getLogPath()]
|
||||||
|
@ -195,17 +196,17 @@ class Server:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def addJournalMatch(self, name, match): # pragma: systemd no cover
|
def addJournalMatch(self, name, match): # pragma: systemd no cover
|
||||||
filter_ = self.__jails.getFilter(name)
|
filter_ = self.__jails[name].filter
|
||||||
if isinstance(filter_, JournalFilter):
|
if isinstance(filter_, JournalFilter):
|
||||||
filter_.addJournalMatch(match)
|
filter_.addJournalMatch(match)
|
||||||
|
|
||||||
def delJournalMatch(self, name, match): # pragma: systemd no cover
|
def delJournalMatch(self, name, match): # pragma: systemd no cover
|
||||||
filter_ = self.__jails.getFilter(name)
|
filter_ = self.__jails[name].filter
|
||||||
if isinstance(filter_, JournalFilter):
|
if isinstance(filter_, JournalFilter):
|
||||||
filter_.delJournalMatch(match)
|
filter_.delJournalMatch(match)
|
||||||
|
|
||||||
def getJournalMatch(self, name): # pragma: systemd no cover
|
def getJournalMatch(self, name): # pragma: systemd no cover
|
||||||
filter_ = self.__jails.getFilter(name)
|
filter_ = self.__jails[name].filter
|
||||||
if isinstance(filter_, JournalFilter):
|
if isinstance(filter_, JournalFilter):
|
||||||
return filter_.getJournalMatch()
|
return filter_.getJournalMatch()
|
||||||
else:
|
else:
|
||||||
|
@ -213,154 +214,109 @@ class Server:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def setLogEncoding(self, name, encoding):
|
def setLogEncoding(self, name, encoding):
|
||||||
filter_ = self.__jails.getFilter(name)
|
filter_ = self.__jails[name].filter
|
||||||
if isinstance(filter_, FileFilter):
|
if isinstance(filter_, FileFilter):
|
||||||
filter_.setLogEncoding(encoding)
|
filter_.setLogEncoding(encoding)
|
||||||
|
|
||||||
def getLogEncoding(self, name):
|
def getLogEncoding(self, name):
|
||||||
filter_ = self.__jails.getFilter(name)
|
filter_ = self.__jails[name].filter
|
||||||
if isinstance(filter_, FileFilter):
|
if isinstance(filter_, FileFilter):
|
||||||
return filter_.getLogEncoding()
|
return filter_.getLogEncoding()
|
||||||
|
|
||||||
def setFindTime(self, name, value):
|
def setFindTime(self, name, value):
|
||||||
self.__jails.getFilter(name).setFindTime(value)
|
self.__jails[name].filter.setFindTime(value)
|
||||||
|
|
||||||
def getFindTime(self, name):
|
def getFindTime(self, name):
|
||||||
return self.__jails.getFilter(name).getFindTime()
|
return self.__jails[name].filter.getFindTime()
|
||||||
|
|
||||||
def setDatePattern(self, name, pattern):
|
def setDatePattern(self, name, pattern):
|
||||||
self.__jails.getFilter(name).setDatePattern(pattern)
|
self.__jails[name].filter.setDatePattern(pattern)
|
||||||
|
|
||||||
def getDatePattern(self, name):
|
def getDatePattern(self, name):
|
||||||
return self.__jails.getFilter(name).getDatePattern()
|
return self.__jails[name].filter.getDatePattern()
|
||||||
|
|
||||||
def setIgnoreCommand(self, name, value):
|
def setIgnoreCommand(self, name, value):
|
||||||
self.__jails.getFilter(name).setIgnoreCommand(value)
|
self.__jails[name].filter.setIgnoreCommand(value)
|
||||||
|
|
||||||
def getIgnoreCommand(self, name):
|
def getIgnoreCommand(self, name):
|
||||||
return self.__jails.getFilter(name).getIgnoreCommand()
|
return self.__jails[name].filter.getIgnoreCommand()
|
||||||
|
|
||||||
def addFailRegex(self, name, value):
|
def addFailRegex(self, name, value):
|
||||||
self.__jails.getFilter(name).addFailRegex(value)
|
self.__jails[name].filter.addFailRegex(value)
|
||||||
|
|
||||||
def delFailRegex(self, name, index):
|
def delFailRegex(self, name, index):
|
||||||
self.__jails.getFilter(name).delFailRegex(index)
|
self.__jails[name].filter.delFailRegex(index)
|
||||||
|
|
||||||
def getFailRegex(self, name):
|
def getFailRegex(self, name):
|
||||||
return self.__jails.getFilter(name).getFailRegex()
|
return self.__jails[name].filter.getFailRegex()
|
||||||
|
|
||||||
def addIgnoreRegex(self, name, value):
|
def addIgnoreRegex(self, name, value):
|
||||||
self.__jails.getFilter(name).addIgnoreRegex(value)
|
self.__jails[name].filter.addIgnoreRegex(value)
|
||||||
|
|
||||||
def delIgnoreRegex(self, name, index):
|
def delIgnoreRegex(self, name, index):
|
||||||
self.__jails.getFilter(name).delIgnoreRegex(index)
|
self.__jails[name].filter.delIgnoreRegex(index)
|
||||||
|
|
||||||
def getIgnoreRegex(self, name):
|
def getIgnoreRegex(self, name):
|
||||||
return self.__jails.getFilter(name).getIgnoreRegex()
|
return self.__jails[name].filter.getIgnoreRegex()
|
||||||
|
|
||||||
def setUseDns(self, name, value):
|
def setUseDns(self, name, value):
|
||||||
self.__jails.getFilter(name).setUseDns(value)
|
self.__jails[name].filter.setUseDns(value)
|
||||||
|
|
||||||
def getUseDns(self, name):
|
def getUseDns(self, name):
|
||||||
return self.__jails.getFilter(name).getUseDns()
|
return self.__jails[name].filter.getUseDns()
|
||||||
|
|
||||||
def setMaxRetry(self, name, value):
|
def setMaxRetry(self, name, value):
|
||||||
self.__jails.getFilter(name).setMaxRetry(value)
|
self.__jails[name].filter.setMaxRetry(value)
|
||||||
|
|
||||||
def getMaxRetry(self, name):
|
def getMaxRetry(self, name):
|
||||||
return self.__jails.getFilter(name).getMaxRetry()
|
return self.__jails[name].filter.getMaxRetry()
|
||||||
|
|
||||||
def setMaxLines(self, name, value):
|
def setMaxLines(self, name, value):
|
||||||
self.__jails.getFilter(name).setMaxLines(value)
|
self.__jails[name].filter.setMaxLines(value)
|
||||||
|
|
||||||
def getMaxLines(self, name):
|
def getMaxLines(self, name):
|
||||||
return self.__jails.getFilter(name).getMaxLines()
|
return self.__jails[name].filter.getMaxLines()
|
||||||
|
|
||||||
# Action
|
# Action
|
||||||
def addAction(self, name, value):
|
def addAction(self, name, value, *args):
|
||||||
self.__jails.getAction(name).addAction(value)
|
self.__jails[name].actions.add(value, *args)
|
||||||
|
|
||||||
def getLastAction(self, name):
|
|
||||||
return self.__jails.getAction(name).getLastAction()
|
|
||||||
|
|
||||||
def getActions(self, name):
|
def getActions(self, name):
|
||||||
return self.__jails.getAction(name).getActions()
|
return self.__jails[name].actions
|
||||||
|
|
||||||
def delAction(self, name, value):
|
def delAction(self, name, value):
|
||||||
self.__jails.getAction(name).delAction(value)
|
del self.__jails[name].actions[value]
|
||||||
|
|
||||||
def setCInfo(self, name, action, key, value):
|
def getAction(self, name, value):
|
||||||
self.__jails.getAction(name).getAction(action).setCInfo(key, value)
|
return self.__jails[name].actions[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 setBanTime(self, name, value):
|
def setBanTime(self, name, value):
|
||||||
self.__jails.getAction(name).setBanTime(value)
|
self.__jails[name].actions.setBanTime(value)
|
||||||
|
|
||||||
def setBanIP(self, name, 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):
|
def setUnbanIP(self, name, value):
|
||||||
return self.__jails.getAction(name).removeBannedIP(value)
|
self.__jails[name].actions.removeBannedIP(value)
|
||||||
|
|
||||||
def getBanTime(self, name):
|
def getBanTime(self, name):
|
||||||
return self.__jails.getAction(name).getBanTime()
|
return self.__jails[name].actions.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()
|
|
||||||
|
|
||||||
# Status
|
# Status
|
||||||
def status(self):
|
def status(self):
|
||||||
try:
|
try:
|
||||||
self.__lock.acquire()
|
self.__lock.acquire()
|
||||||
jails = list(self.__jails.getAll())
|
jails = list(self.__jails)
|
||||||
jails.sort()
|
jails.sort()
|
||||||
jailList = ", ".join(jails)
|
jailList = ", ".join(jails)
|
||||||
ret = [("Number of jail", self.__jails.size()),
|
ret = [("Number of jail", len(self.__jails)),
|
||||||
("Jail list", jailList)]
|
("Jail list", jailList)]
|
||||||
return ret
|
return ret
|
||||||
finally:
|
finally:
|
||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
|
|
||||||
def statusJail(self, name):
|
def statusJail(self, name):
|
||||||
return self.__jails.get(name).getStatus()
|
return self.__jails[name].getStatus()
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
|
|
||||||
|
@ -488,7 +444,7 @@ class Server:
|
||||||
return "flushed"
|
return "flushed"
|
||||||
|
|
||||||
def setDatabase(self, filename):
|
def setDatabase(self, filename):
|
||||||
if self.__jails.size() == 0:
|
if len(self.__jails) == 0:
|
||||||
if filename.lower() == "none":
|
if filename.lower() == "none":
|
||||||
self.__db = None
|
self.__db = None
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -25,6 +25,7 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import logging, time
|
import logging, time
|
||||||
|
import json
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
@ -226,56 +227,29 @@ class Transmitter:
|
||||||
return self.__server.setBanIP(name,value)
|
return self.__server.setBanIP(name,value)
|
||||||
elif command[1] == "unbanip":
|
elif command[1] == "unbanip":
|
||||||
value = command[2]
|
value = command[2]
|
||||||
return self.__server.setUnbanIP(name,value)
|
self.__server.setUnbanIP(name, value)
|
||||||
|
return value
|
||||||
elif command[1] == "addaction":
|
elif command[1] == "addaction":
|
||||||
value = command[2]
|
args = [command[2]]
|
||||||
self.__server.addAction(name, value)
|
if len(command) > 3:
|
||||||
return self.__server.getLastAction(name).getName()
|
args.extend([command[3], json.loads(command[4])])
|
||||||
|
self.__server.addAction(name, *args)
|
||||||
|
return args[0]
|
||||||
elif command[1] == "delaction":
|
elif command[1] == "delaction":
|
||||||
value = command[2]
|
value = command[2]
|
||||||
self.__server.delAction(name, value)
|
self.__server.delAction(name, value)
|
||||||
return None
|
return None
|
||||||
elif command[1] == "setcinfo":
|
elif command[1] == "action":
|
||||||
act = command[2]
|
actionname = command[2]
|
||||||
key = command[3]
|
actionkey = command[3]
|
||||||
value = " ".join(command[4:])
|
action = self.__server.getAction(name, actionname)
|
||||||
self.__server.setCInfo(name, act, key, value)
|
if callable(getattr(action, actionkey, None)):
|
||||||
return self.__server.getCInfo(name, act, key)
|
actionvalue = json.loads(command[4]) if len(command)>4 else {}
|
||||||
elif command[1] == "delcinfo":
|
return getattr(action, actionkey)(**actionvalue)
|
||||||
act = command[2]
|
else:
|
||||||
key = command[3]
|
actionvalue = command[4]
|
||||||
self.__server.delCInfo(name, act, key)
|
setattr(action, actionkey, actionvalue)
|
||||||
return None
|
return getattr(action, actionkey)
|
||||||
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)
|
|
||||||
raise Exception("Invalid command (no set action or not yet implemented)")
|
raise Exception("Invalid command (no set action or not yet implemented)")
|
||||||
|
|
||||||
def __commandGet(self, command):
|
def __commandGet(self, command):
|
||||||
|
@ -327,31 +301,25 @@ class Transmitter:
|
||||||
elif command[1] == "bantime":
|
elif command[1] == "bantime":
|
||||||
return self.__server.getBanTime(name)
|
return self.__server.getBanTime(name)
|
||||||
elif command[1] == "actions":
|
elif command[1] == "actions":
|
||||||
return self.__server.getActions(name)
|
return self.__server.getActions(name).keys()
|
||||||
elif command[1] == "addaction":
|
elif command[1] == "action":
|
||||||
return self.__server.getLastAction(name).getName()
|
actionname = command[2]
|
||||||
elif command[1] == "actionstart":
|
actionvalue = command[3]
|
||||||
act = command[2]
|
action = self.__server.getAction(name, actionname)
|
||||||
return self.__server.getActionStart(name, act)
|
return getattr(action, actionvalue)
|
||||||
elif command[1] == "actionstop":
|
elif command[1] == "actionproperties":
|
||||||
act = command[2]
|
actionname = command[2]
|
||||||
return self.__server.getActionStop(name, act)
|
action = self.__server.getAction(name, actionname)
|
||||||
elif command[1] == "actioncheck":
|
return [
|
||||||
act = command[2]
|
key for key in dir(action)
|
||||||
return self.__server.getActionCheck(name, act)
|
if not key.startswith("_") and
|
||||||
elif command[1] == "actionban":
|
not callable(getattr(action, key))]
|
||||||
act = command[2]
|
elif command[1] == "actionmethods":
|
||||||
return self.__server.getActionBan(name, act)
|
actionname = command[2]
|
||||||
elif command[1] == "actionunban":
|
action = self.__server.getAction(name, actionname)
|
||||||
act = command[2]
|
return [
|
||||||
return self.__server.getActionUnban(name, act)
|
key for key in dir(action)
|
||||||
elif command[1] == "cinfo":
|
if not key.startswith("_") and callable(getattr(action, key))]
|
||||||
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)
|
|
||||||
raise Exception("Invalid command (no get action or not yet implemented)")
|
raise Exception("Invalid command (no get action or not yet implemented)")
|
||||||
|
|
||||||
def status(self, command):
|
def status(self, command):
|
||||||
|
|
|
@ -26,39 +26,49 @@ __license__ = "GPL"
|
||||||
|
|
||||||
import unittest, time
|
import unittest, time
|
||||||
import sys, os, tempfile
|
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):
|
def setUp(self):
|
||||||
"""Call before every test case."""
|
"""Call before every test case."""
|
||||||
|
super(ExecuteActions, self).setUp()
|
||||||
self.__jail = DummyJail()
|
self.__jail = DummyJail()
|
||||||
self.__actions = Actions(self.__jail)
|
self.__actions = Actions(self.__jail)
|
||||||
self.__tmpfile, self.__tmpfilename = tempfile.mkstemp()
|
self.__tmpfile, self.__tmpfilename = tempfile.mkstemp()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
super(ExecuteActions, self).tearDown()
|
||||||
os.remove(self.__tmpfilename)
|
os.remove(self.__tmpfilename)
|
||||||
|
|
||||||
def defaultActions(self):
|
def defaultActions(self):
|
||||||
self.__actions.addAction('ip')
|
self.__actions.add('ip')
|
||||||
self.__ip = self.__actions.getAction('ip')
|
self.__ip = self.__actions['ip']
|
||||||
self.__ip.setActionStart('echo ip start 64 >> "%s"' % self.__tmpfilename )
|
self.__ip.actionstart = 'echo ip start 64 >> "%s"' % self.__tmpfilename
|
||||||
self.__ip.setActionBan('echo ip ban <ip> >> "%s"' % self.__tmpfilename )
|
self.__ip.actionban = 'echo ip ban <ip> >> "%s"' % self.__tmpfilename
|
||||||
self.__ip.setActionUnban('echo ip unban <ip> >> "%s"' % self.__tmpfilename )
|
self.__ip.actionunban = 'echo ip unban <ip> >> "%s"' % self.__tmpfilename
|
||||||
self.__ip.setActionCheck('echo ip check <ip> >> "%s"' % self.__tmpfilename )
|
self.__ip.actioncheck = 'echo ip check <ip> >> "%s"' % self.__tmpfilename
|
||||||
self.__ip.setActionStop('echo ip stop >> "%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):
|
def testActionsManipulation(self):
|
||||||
self.__actions.addAction('test')
|
self.__actions.add('test')
|
||||||
self.assertTrue(self.__actions.getAction('test'))
|
self.assertTrue(self.__actions['test'])
|
||||||
self.assertTrue(self.__actions.getLastAction())
|
self.assertTrue('test' in self.__actions)
|
||||||
self.assertRaises(KeyError,self.__actions.getAction,*['nonexistant action'])
|
self.assertFalse('nonexistant action' in self.__actions)
|
||||||
self.__actions.addAction('test1')
|
self.__actions.add('test1')
|
||||||
self.__actions.delAction('test')
|
del self.__actions['test']
|
||||||
self.__actions.delAction('test1')
|
del self.__actions['test1']
|
||||||
self.assertRaises(KeyError, self.__actions.getAction, *['test'])
|
self.assertFalse('test' in self.__actions)
|
||||||
self.assertRaises(IndexError,self.__actions.getLastAction)
|
self.assertEqual(len(self.__actions), 0)
|
||||||
|
|
||||||
self.__actions.setBanTime(127)
|
self.__actions.setBanTime(127)
|
||||||
self.assertEqual(self.__actions.getBanTime(),127)
|
self.assertEqual(self.__actions.getBanTime(),127)
|
||||||
|
@ -77,3 +87,55 @@ class ExecuteActions(unittest.TestCase):
|
||||||
self.assertEqual(self.__actions.status(),[("Currently banned", 0 ),
|
self.assertEqual(self.__actions.status(),[("Currently banned", 0 ),
|
||||||
("Total banned", 0 ), ("IP list", [] )])
|
("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,27 +27,22 @@ __license__ = "GPL"
|
||||||
import time
|
import time
|
||||||
import logging, sys
|
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):
|
def setUp(self):
|
||||||
"""Call before every test case."""
|
"""Call before every test case."""
|
||||||
self.__action = Action("Test")
|
self.__action = CommandAction(None, "Test")
|
||||||
LogCaptureTestCase.setUp(self)
|
LogCaptureTestCase.setUp(self)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
"""Call after every test case."""
|
"""Call after every test case."""
|
||||||
LogCaptureTestCase.tearDown(self)
|
LogCaptureTestCase.tearDown(self)
|
||||||
self.__action.execActionStop()
|
self.__action.stop()
|
||||||
|
|
||||||
def testNameChange(self):
|
|
||||||
self.assertEqual(self.__action.getName(), "Test")
|
|
||||||
self.__action.setName("Tricky Test")
|
|
||||||
self.assertEqual(self.__action.getName(), "Tricky Test")
|
|
||||||
|
|
||||||
def testSubstituteRecursiveTags(self):
|
def testSubstituteRecursiveTags(self):
|
||||||
aInfo = {
|
aInfo = {
|
||||||
'HOST': "192.0.2.0",
|
'HOST': "192.0.2.0",
|
||||||
|
@ -55,18 +50,18 @@ class ExecuteAction(LogCaptureTestCase):
|
||||||
'xyz': "890 <ABC>",
|
'xyz': "890 <ABC>",
|
||||||
}
|
}
|
||||||
# Recursion is bad
|
# Recursion is bad
|
||||||
self.assertFalse(Action.substituteRecursiveTags({'A': '<A>'}))
|
self.assertFalse(CommandAction.substituteRecursiveTags({'A': '<A>'}))
|
||||||
self.assertFalse(Action.substituteRecursiveTags({'A': '<B>', 'B': '<A>'}))
|
self.assertFalse(CommandAction.substituteRecursiveTags({'A': '<B>', 'B': '<A>'}))
|
||||||
self.assertFalse(Action.substituteRecursiveTags({'A': '<B>', 'B': '<C>', 'C': '<A>'}))
|
self.assertFalse(CommandAction.substituteRecursiveTags({'A': '<B>', 'B': '<C>', 'C': '<A>'}))
|
||||||
# part recursion
|
# Unresolveable substition
|
||||||
self.assertFalse(Action.substituteRecursiveTags({'A': 'to=<B> fromip=<IP>', 'C': '<B>', 'B': '<C>', 'D': ''}))
|
self.assertFalse(CommandAction.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({'failregex': 'to=<honeypot> fromip=<IP>', 'sweet': '<honeypot>', 'honeypot': '<sweet>', 'ignoreregex': ''}))
|
||||||
# missing tags are ok
|
# missing tags are ok
|
||||||
self.assertEqual(Action.substituteRecursiveTags({'A': '<C>'}), {'A': '<C>'})
|
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C>'}), {'A': '<C>'})
|
||||||
self.assertEqual(Action.substituteRecursiveTags({'A': '<C> <D> <X>','X':'fun'}), {'A': '<C> <D> fun', 'X':'fun'})
|
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C> <D> <X>','X':'fun'}), {'A': '<C> <D> fun', 'X':'fun'})
|
||||||
self.assertEqual(Action.substituteRecursiveTags({'A': '<C> <B>', 'B': 'cool'}), {'A': '<C> cool', 'B': 'cool'})
|
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C> <B>', 'B': 'cool'}), {'A': '<C> cool', 'B': 'cool'})
|
||||||
# rest is just cool
|
# rest is just cool
|
||||||
self.assertEqual(Action.substituteRecursiveTags(aInfo),
|
self.assertEqual(CommandAction.substituteRecursiveTags(aInfo),
|
||||||
{ 'HOST': "192.0.2.0",
|
{ 'HOST': "192.0.2.0",
|
||||||
'ABC': '123 192.0.2.0',
|
'ABC': '123 192.0.2.0',
|
||||||
'xyz': '890 123 192.0.2.0',
|
'xyz': '890 123 192.0.2.0',
|
||||||
|
@ -102,90 +97,102 @@ class ExecuteAction(LogCaptureTestCase):
|
||||||
|
|
||||||
# Callable
|
# Callable
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.__action.replaceTag("09 <callable> 11",
|
self.__action.replaceTag("09 <callme> 11",
|
||||||
{'callable': lambda: str(10)}),
|
CallingMap(callme=lambda: str(10))),
|
||||||
"09 10 11")
|
"09 10 11")
|
||||||
|
|
||||||
# As tag not present, therefore callable should not be called
|
# As tag not present, therefore callable should not be called
|
||||||
# Will raise ValueError if it is
|
# Will raise ValueError if it is
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.__action.replaceTag("abc",
|
self.__action.replaceTag("abc",
|
||||||
{'callable': lambda: int("a")}), "abc")
|
CallingMap(callme=lambda: int("a"))), "abc")
|
||||||
|
|
||||||
def testExecuteActionBan(self):
|
def testExecuteActionBan(self):
|
||||||
self.__action.setActionStart("touch /tmp/fail2ban.test")
|
self.__action.actionstart = "touch /tmp/fail2ban.test"
|
||||||
self.assertEqual(self.__action.getActionStart(), "touch /tmp/fail2ban.test")
|
self.assertEqual(self.__action.actionstart, "touch /tmp/fail2ban.test")
|
||||||
self.__action.setActionStop("rm -f /tmp/fail2ban.test")
|
self.__action.actionstop = "rm -f /tmp/fail2ban.test"
|
||||||
self.assertEqual(self.__action.getActionStop(), 'rm -f /tmp/fail2ban.test')
|
self.assertEqual(self.__action.actionstop, 'rm -f /tmp/fail2ban.test')
|
||||||
self.__action.setActionBan("echo -n")
|
self.__action.actionban = "echo -n"
|
||||||
self.assertEqual(self.__action.getActionBan(), 'echo -n')
|
self.assertEqual(self.__action.actionban, 'echo -n')
|
||||||
self.__action.setActionCheck("[ -e /tmp/fail2ban.test ]")
|
self.__action.actioncheck = "[ -e /tmp/fail2ban.test ]"
|
||||||
self.assertEqual(self.__action.getActionCheck(), '[ -e /tmp/fail2ban.test ]')
|
self.assertEqual(self.__action.actioncheck, '[ -e /tmp/fail2ban.test ]')
|
||||||
self.__action.setActionUnban("true")
|
self.__action.actionunban = "true"
|
||||||
self.assertEqual(self.__action.getActionUnban(), 'true')
|
self.assertEqual(self.__action.actionunban, 'true')
|
||||||
|
|
||||||
self.assertFalse(self._is_logged('returned'))
|
self.assertFalse(self._is_logged('returned'))
|
||||||
# no action was actually executed yet
|
# 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('Invariant check failed'))
|
||||||
self.assertTrue(self._is_logged('returned successfully'))
|
self.assertTrue(self._is_logged('returned successfully'))
|
||||||
|
|
||||||
def testExecuteActionEmptyUnban(self):
|
def testExecuteActionEmptyUnban(self):
|
||||||
self.__action.setActionUnban("")
|
self.__action.actionunban = ""
|
||||||
self.assertTrue(self.__action.execActionUnban(None))
|
self.__action.unban({})
|
||||||
self.assertTrue(self._is_logged('Nothing to do'))
|
self.assertTrue(self._is_logged('Nothing to do'))
|
||||||
|
|
||||||
def testExecuteActionStartCtags(self):
|
def testExecuteActionStartCtags(self):
|
||||||
self.__action.setCInfo("HOST","192.0.2.0")
|
self.__action.HOST = "192.0.2.0"
|
||||||
self.__action.setActionStart("touch /tmp/fail2ban.test.<HOST>")
|
self.__action.actionstart = "touch /tmp/fail2ban.test.<HOST>"
|
||||||
self.__action.setActionStop("rm -f /tmp/fail2ban.test.<HOST>")
|
self.__action.actionstop = "rm -f /tmp/fail2ban.test.<HOST>"
|
||||||
self.__action.setActionCheck("[ -e /tmp/fail2ban.test.192.0.2.0 ]")
|
self.__action.actioncheck = "[ -e /tmp/fail2ban.test.192.0.2.0 ]"
|
||||||
self.assertTrue(self.__action.execActionStart())
|
self.__action.start()
|
||||||
|
|
||||||
def testExecuteActionCheckRestoreEnvironment(self):
|
def testExecuteActionCheckRestoreEnvironment(self):
|
||||||
self.__action.setActionStart("")
|
self.__action.actionstart = ""
|
||||||
self.__action.setActionStop("rm -f /tmp/fail2ban.test")
|
self.__action.actionstop = "rm -f /tmp/fail2ban.test"
|
||||||
self.__action.setActionBan("rm /tmp/fail2ban.test")
|
self.__action.actionban = "rm /tmp/fail2ban.test"
|
||||||
self.__action.setActionCheck("[ -e /tmp/fail2ban.test ]")
|
self.__action.actioncheck = "[ -e /tmp/fail2ban.test ]"
|
||||||
self.assertFalse(self.__action.execActionBan(None))
|
self.assertRaises(RuntimeError, self.__action.ban, {'ip': None})
|
||||||
self.assertTrue(self._is_logged('Unable to restore environment'))
|
self.assertTrue(self._is_logged('Unable to restore environment'))
|
||||||
|
|
||||||
def testExecuteActionChangeCtags(self):
|
def testExecuteActionChangeCtags(self):
|
||||||
self.__action.setCInfo("ROST","192.0.2.0")
|
self.assertRaises(AttributeError, getattr, self.__action, "ROST")
|
||||||
self.assertEqual(self.__action.getCInfo("ROST"),"192.0.2.0")
|
self.__action.ROST = "192.0.2.0"
|
||||||
self.__action.delCInfo("ROST")
|
self.assertEqual(self.__action.ROST,"192.0.2.0")
|
||||||
self.assertRaises(KeyError, self.__action.getCInfo, "ROST")
|
|
||||||
|
|
||||||
def testExecuteActionUnbanAinfo(self):
|
def testExecuteActionUnbanAinfo(self):
|
||||||
aInfo = {
|
aInfo = {
|
||||||
'ABC': "123",
|
'ABC': "123",
|
||||||
}
|
}
|
||||||
self.__action.setActionBan("touch /tmp/fail2ban.test.123")
|
self.__action.actionban = "touch /tmp/fail2ban.test.123"
|
||||||
self.__action.setActionUnban("rm /tmp/fail2ban.test.<ABC>")
|
self.__action.actionunban = "rm /tmp/fail2ban.test.<ABC>"
|
||||||
self.assertTrue(self.__action.execActionBan(None))
|
self.__action.ban(aInfo)
|
||||||
self.assertTrue(self.__action.execActionUnban(aInfo))
|
self.__action.unban(aInfo)
|
||||||
|
|
||||||
def testExecuteActionStartEmpty(self):
|
def testExecuteActionStartEmpty(self):
|
||||||
self.__action.setActionStart("")
|
self.__action.actionstart = ""
|
||||||
self.assertTrue(self.__action.execActionStart())
|
self.__action.start()
|
||||||
self.assertTrue(self._is_logged('Nothing to do'))
|
self.assertTrue(self._is_logged('Nothing to do'))
|
||||||
|
|
||||||
def testExecuteIncorrectCmd(self):
|
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"'))
|
self.assertTrue(self._is_logged('HINT on 127: "Command not found"'))
|
||||||
|
|
||||||
def testExecuteTimeout(self):
|
def testExecuteTimeout(self):
|
||||||
stime = time.time()
|
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.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 -- timed out after 2 seconds'))
|
||||||
self.assertTrue(self._is_logged('sleep 60 -- killed with SIGTERM'))
|
self.assertTrue(self._is_logged('sleep 60 -- killed with SIGTERM'))
|
||||||
|
|
||||||
def testCaptureStdOutErr(self):
|
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'"))
|
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')
|
'echo "The rain in Spain stays mainly in the plain" 1>&2')
|
||||||
self.assertTrue(self._is_logged(
|
self.assertTrue(self._is_logged(
|
||||||
"'The rain in Spain stays mainly in the plain\\n'"))
|
"'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
|
import unittest
|
||||||
|
|
||||||
from fail2ban.server.banmanager import BanManager
|
from ..server.banmanager import BanManager
|
||||||
from fail2ban.server.ticket import BanTicket
|
from ..server.ticket import BanTicket
|
||||||
|
|
||||||
class AddFailure(unittest.TestCase):
|
class AddFailure(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -23,19 +23,22 @@ __license__ = "GPL"
|
||||||
|
|
||||||
import os, shutil, sys, tempfile, unittest
|
import os, shutil, sys, tempfile, unittest
|
||||||
|
|
||||||
from fail2ban.client.configreader import ConfigReader
|
from ..client.configreader import ConfigReader
|
||||||
from fail2ban.client.jailreader import JailReader
|
from ..client.jailreader import JailReader
|
||||||
from fail2ban.client.filterreader import FilterReader
|
from ..client.filterreader import FilterReader
|
||||||
from fail2ban.client.jailsreader import JailsReader
|
from ..client.jailsreader import JailsReader
|
||||||
from fail2ban.client.actionreader import ActionReader
|
from ..client.actionreader import ActionReader
|
||||||
from fail2ban.client.configurator import Configurator
|
from ..client.configurator import Configurator
|
||||||
from fail2ban.tests.utils import LogCaptureTestCase
|
from .utils import LogCaptureTestCase
|
||||||
|
|
||||||
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
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'
|
CONFIG_DIR='config'
|
||||||
else:
|
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')
|
IMPERFECT_CONFIG = os.path.join('fail2ban', 'tests','config')
|
||||||
|
|
||||||
|
@ -381,13 +384,18 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||||
['set', 'brokenaction', 'addaction', 'brokenaction'],
|
['set', 'brokenaction', 'addaction', 'brokenaction'],
|
||||||
['set',
|
['set',
|
||||||
'brokenaction',
|
'brokenaction',
|
||||||
'actionban',
|
'action',
|
||||||
'brokenaction',
|
'brokenaction',
|
||||||
|
'actionban',
|
||||||
'hit with big stick <ip>'],
|
'hit with big stick <ip>'],
|
||||||
['set', 'brokenaction', 'actionstop', 'brokenaction', ''],
|
['set', 'brokenaction', 'action', 'brokenaction',
|
||||||
['set', 'brokenaction', 'actionstart', 'brokenaction', ''],
|
'actionstop', ''],
|
||||||
['set', 'brokenaction', 'actionunban', 'brokenaction', ''],
|
['set', 'brokenaction', 'action', 'brokenaction',
|
||||||
['set', 'brokenaction', 'actioncheck', 'brokenaction', ''],
|
'actionstart', ''],
|
||||||
|
['set', 'brokenaction', 'action', 'brokenaction',
|
||||||
|
'actionunban', ''],
|
||||||
|
['set', 'brokenaction', 'action', 'brokenaction',
|
||||||
|
'actioncheck', ''],
|
||||||
['add', 'parse_to_end_of_jail.conf', 'auto'],
|
['add', 'parse_to_end_of_jail.conf', 'auto'],
|
||||||
['set', 'parse_to_end_of_jail.conf', 'usedns', 'warn'],
|
['set', 'parse_to_end_of_jail.conf', 'usedns', 'warn'],
|
||||||
['set', 'parse_to_end_of_jail.conf', 'maxretry', 3],
|
['set', 'parse_to_end_of_jail.conf', 'maxretry', 3],
|
||||||
|
@ -514,7 +522,7 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||||
self.assertTrue('blocktype' in action._initOpts)
|
self.assertTrue('blocktype' in action._initOpts)
|
||||||
# Verify that we have a call to set it up
|
# Verify that we have a call to set it up
|
||||||
blocktype_present = False
|
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:
|
for command in commands:
|
||||||
if (len(command) > 5 and
|
if (len(command) > 5 and
|
||||||
command[:5] == target_command):
|
command[:5] == target_command):
|
||||||
|
@ -567,6 +575,8 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||||
[testjail1]
|
[testjail1]
|
||||||
action = testaction1[actname=test1]
|
action = testaction1[actname=test1]
|
||||||
testaction1[actname=test2]
|
testaction1[actname=test2]
|
||||||
|
testaction.py
|
||||||
|
testaction.py[actname=test3]
|
||||||
filter = testfilter1
|
filter = testfilter1
|
||||||
""")
|
""")
|
||||||
jailfd.close()
|
jailfd.close()
|
||||||
|
@ -575,8 +585,12 @@ filter = testfilter1
|
||||||
self.assertTrue(jails.getOptions())
|
self.assertTrue(jails.getOptions())
|
||||||
comm_commands = jails.convert(allow_no_files=True)
|
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)
|
shutil.rmtree(basedir)
|
||||||
|
|
|
@ -28,11 +28,11 @@ import tempfile
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from fail2ban.server.database import Fail2BanDb
|
from ..server.database import Fail2BanDb
|
||||||
from fail2ban.server.filter import FileContainer
|
from ..server.filter import FileContainer
|
||||||
from fail2ban.server.mytime import MyTime
|
from ..server.mytime import MyTime
|
||||||
from fail2ban.server.ticket import FailTicket
|
from ..server.ticket import FailTicket
|
||||||
from fail2ban.tests.dummyjail import DummyJail
|
from .dummyjail import DummyJail
|
||||||
|
|
||||||
class DatabaseTest(unittest.TestCase):
|
class DatabaseTest(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -25,10 +25,10 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import unittest, calendar, time, datetime, re, pprint
|
import unittest, calendar, time, datetime, re, pprint
|
||||||
from fail2ban.server.datedetector import DateDetector
|
from ..server.datedetector import DateDetector
|
||||||
from fail2ban.server.datetemplate import DateTemplate
|
from ..server.datetemplate import DateTemplate
|
||||||
from fail2ban.server.iso8601 import Utc
|
from ..server.iso8601 import Utc
|
||||||
from fail2ban.tests.utils import setUpMyTime, tearDownMyTime
|
from .utils import setUpMyTime, tearDownMyTime
|
||||||
|
|
||||||
class DateDetectorTest(unittest.TestCase):
|
class DateDetectorTest(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,8 @@ __license__ = "GPL"
|
||||||
|
|
||||||
import unittest, socket, time, pickle
|
import unittest, socket, time, pickle
|
||||||
|
|
||||||
from fail2ban.server.failmanager import FailManager, FailManagerEmpty
|
from ..server.failmanager import FailManager, FailManagerEmpty
|
||||||
from fail2ban.server.ticket import FailTicket
|
from ..server.ticket import FailTicket
|
||||||
|
|
||||||
class AddFailure(unittest.TestCase):
|
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
|
# https://github.com/fail2ban/fail2ban/issues/541
|
||||||
# failJSON: { "time": "2013-12-30T00:24:50", "match": true , "host": "178.123.108.196" }
|
# 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
|
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:
|
except ImportError:
|
||||||
journal = None
|
journal = None
|
||||||
|
|
||||||
from fail2ban.server.jail import Jail
|
from ..server.jail import Jail
|
||||||
from fail2ban.server.filterpoll import FilterPoll
|
from ..server.filterpoll import FilterPoll
|
||||||
from fail2ban.server.filter import Filter, FileFilter, DNSUtils
|
from ..server.filter import Filter, FileFilter, DNSUtils
|
||||||
from fail2ban.server.failmanager import FailManager
|
from ..server.failmanager import FailManager, FailManagerEmpty
|
||||||
from fail2ban.server.failmanager import FailManagerEmpty
|
from ..server.mytime import MyTime
|
||||||
from fail2ban.server.mytime import MyTime
|
from .utils import setUpMyTime, tearDownMyTime, mtimesleep, LogCaptureTestCase
|
||||||
from fail2ban.tests.utils import setUpMyTime, tearDownMyTime
|
from .dummyjail import DummyJail
|
||||||
from fail2ban.tests.utils import mtimesleep, LogCaptureTestCase
|
|
||||||
|
|
||||||
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
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
|
# yoh: per Steven Hiscocks's insight while troubleshooting
|
||||||
# https://github.com/fail2ban/fail2ban/issues/103#issuecomment-15542836
|
# https://github.com/fail2ban/fail2ban/issues/103#issuecomment-15542836
|
||||||
# adding a sufficiently large buffer might help to guarantee that
|
# adding a sufficiently large buffer might help to guarantee that
|
||||||
|
|
|
@ -28,8 +28,8 @@ import shutil
|
||||||
|
|
||||||
from glob import glob
|
from glob import glob
|
||||||
|
|
||||||
from utils import mbasename, TraceBack, FormatterWithTraceBack
|
from .utils import mbasename, TraceBack, FormatterWithTraceBack
|
||||||
from fail2ban.helpers import formatExceptionInfo
|
from ..helpers import formatExceptionInfo
|
||||||
|
|
||||||
class HelpersTest(unittest.TestCase):
|
class HelpersTest(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -30,9 +30,9 @@ else:
|
||||||
import simplejson as json
|
import simplejson as json
|
||||||
next = lambda x: x.next()
|
next = lambda x: x.next()
|
||||||
|
|
||||||
from fail2ban.server.filter import Filter
|
from ..server.filter import Filter
|
||||||
from fail2ban.client.filterreader import FilterReader
|
from ..client.filterreader import FilterReader
|
||||||
from fail2ban.tests.utils import setUpMyTime, tearDownMyTime
|
from .utils import setUpMyTime, tearDownMyTime
|
||||||
|
|
||||||
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
||||||
if os.path.exists('config/fail2ban.conf'):
|
if os.path.exists('config/fail2ban.conf'):
|
||||||
|
@ -67,6 +67,7 @@ def testSampleRegexsFactory(name):
|
||||||
# Check filter exists
|
# Check filter exists
|
||||||
filterConf = FilterReader(name, "jail", {}, basedir=CONFIG_DIR)
|
filterConf = FilterReader(name, "jail", {}, basedir=CONFIG_DIR)
|
||||||
self.assertEqual(filterConf.getFile(), name)
|
self.assertEqual(filterConf.getFile(), name)
|
||||||
|
self.assertEqual(filterConf.getJailName(), "jail")
|
||||||
filterConf.read()
|
filterConf.read()
|
||||||
filterConf.getOptions({})
|
filterConf.getOptions({})
|
||||||
|
|
||||||
|
|
|
@ -26,11 +26,11 @@ __license__ = "GPL"
|
||||||
|
|
||||||
import unittest, socket, time, tempfile, os, locale, sys, logging
|
import unittest, socket, time, tempfile, os, locale, sys, logging
|
||||||
|
|
||||||
from fail2ban.server.failregex import Regex, FailRegex, RegexException
|
from ..server.failregex import Regex, FailRegex, RegexException
|
||||||
from fail2ban.server.server import Server, logSys
|
from ..server.server import Server, logSys
|
||||||
from fail2ban.server.jail import Jail
|
from ..server.jail import Jail
|
||||||
from fail2ban.exceptions import UnknownJailException
|
from ..exceptions import UnknownJailException
|
||||||
from fail2ban.tests.utils import LogCaptureTestCase
|
from .utils import LogCaptureTestCase
|
||||||
#from bin.fail2ban-client import Fail2banClient
|
#from bin.fail2ban-client import Fail2banClient
|
||||||
try:
|
try:
|
||||||
from fail2ban.server import filtersystemd
|
from fail2ban.server import filtersystemd
|
||||||
|
@ -518,45 +518,38 @@ class Transmitter(TransmitterBase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(["set", self.jailName, "addaction", action]),
|
self.transm.proceed(["set", self.jailName, "addaction", action]),
|
||||||
(0, action))
|
(0, action))
|
||||||
self.assertEqual(
|
|
||||||
self.transm.proceed(["get", self.jailName, "addaction"]),
|
|
||||||
(0, action))
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(
|
self.transm.proceed(
|
||||||
["get", self.jailName, "actions"])[1][0].getName(),
|
["get", self.jailName, "actions"])[1][0],
|
||||||
action)
|
action)
|
||||||
for cmd, value in zip(cmdList, cmdValueList):
|
for cmd, value in zip(cmdList, cmdValueList):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(
|
self.transm.proceed(
|
||||||
["set", self.jailName, cmd, action, value]),
|
["set", self.jailName, "action", action, cmd, value]),
|
||||||
(0, value))
|
(0, value))
|
||||||
for cmd, value in zip(cmdList, cmdValueList):
|
for cmd, value in zip(cmdList, cmdValueList):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(["get", self.jailName, cmd, action]),
|
self.transm.proceed(["get", self.jailName, "action", action, cmd]),
|
||||||
(0, value))
|
(0, value))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(
|
self.transm.proceed(
|
||||||
["set", self.jailName, "setcinfo", action, "KEY", "VALUE"]),
|
["set", self.jailName, "action", action, "KEY", "VALUE"]),
|
||||||
(0, "VALUE"))
|
(0, "VALUE"))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(
|
self.transm.proceed(
|
||||||
["get", self.jailName, "cinfo", action, "KEY"]),
|
["get", self.jailName, "action", action, "KEY"]),
|
||||||
(0, "VALUE"))
|
(0, "VALUE"))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(
|
self.transm.proceed(
|
||||||
["get", self.jailName, "cinfo", action, "InvalidKey"])[0],
|
["get", self.jailName, "action", action, "InvalidKey"])[0],
|
||||||
1)
|
1)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(
|
self.transm.proceed(
|
||||||
["set", self.jailName, "delcinfo", action, "KEY"]),
|
["set", self.jailName, "action", action, "timeout", "10"]),
|
||||||
(0, None))
|
|
||||||
self.assertEqual(
|
|
||||||
self.transm.proceed(
|
|
||||||
["set", self.jailName, "timeout", action, "10"]),
|
|
||||||
(0, 10))
|
(0, 10))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(
|
self.transm.proceed(
|
||||||
["get", self.jailName, "timeout", action]),
|
["get", self.jailName, "action", action, "timeout"]),
|
||||||
(0, 10))
|
(0, 10))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(["set", self.jailName, "delaction", action]),
|
self.transm.proceed(["set", self.jailName, "delaction", action]),
|
||||||
|
@ -565,6 +558,42 @@ class Transmitter(TransmitterBase):
|
||||||
self.transm.proceed(
|
self.transm.proceed(
|
||||||
["set", self.jailName, "delaction", "Doesn't exist"])[0],1)
|
["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):
|
def testNOK(self):
|
||||||
self.assertEqual(self.transm.proceed(["INVALID", "COMMAND"])[0],1)
|
self.assertEqual(self.transm.proceed(["INVALID", "COMMAND"])[0],1)
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,8 @@ __license__ = "GPL"
|
||||||
|
|
||||||
import unittest, time, tempfile, os, threading
|
import unittest, time, tempfile, os, threading
|
||||||
|
|
||||||
from fail2ban.server.asyncserver import AsyncServer, AsyncServerException
|
from ..server.asyncserver import AsyncServer, AsyncServerException
|
||||||
from fail2ban.client.csocket import CSocket
|
from ..client.csocket import CSocket
|
||||||
|
|
||||||
class Socket(unittest.TestCase):
|
class Socket(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -25,16 +25,9 @@ __license__ = "GPL"
|
||||||
import logging, os, re, traceback, time, unittest, sys
|
import logging, os, re, traceback, time, unittest, sys
|
||||||
from os.path import basename, dirname
|
from os.path import basename, dirname
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
|
import json
|
||||||
|
|
||||||
if sys.version_info >= (2, 6):
|
from ..server.mytime import MyTime
|
||||||
import json
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
import simplejson as json
|
|
||||||
except ImportError:
|
|
||||||
json = None
|
|
||||||
|
|
||||||
from fail2ban.server.mytime import MyTime
|
|
||||||
|
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -138,19 +131,18 @@ def tearDownMyTime():
|
||||||
def gatherTests(regexps=None, no_network=False):
|
def gatherTests(regexps=None, no_network=False):
|
||||||
# Import all the test cases here instead of a module level to
|
# Import all the test cases here instead of a module level to
|
||||||
# avoid circular imports
|
# avoid circular imports
|
||||||
from fail2ban.tests import banmanagertestcase
|
from . import banmanagertestcase
|
||||||
from fail2ban.tests import clientreadertestcase
|
from . import clientreadertestcase
|
||||||
from fail2ban.tests import failmanagertestcase
|
from . import failmanagertestcase
|
||||||
from fail2ban.tests import filtertestcase
|
from . import filtertestcase
|
||||||
from fail2ban.tests import servertestcase
|
from . import servertestcase
|
||||||
from fail2ban.tests import datedetectortestcase
|
from . import datedetectortestcase
|
||||||
from fail2ban.tests import actiontestcase
|
from . import actiontestcase
|
||||||
from fail2ban.tests import actionstestcase
|
from . import actionstestcase
|
||||||
from fail2ban.tests import sockettestcase
|
from . import sockettestcase
|
||||||
from fail2ban.tests import misctestcase
|
from . import misctestcase
|
||||||
from fail2ban.tests import databasetestcase
|
from . import databasetestcase
|
||||||
if json:
|
from . import samplestestcase
|
||||||
from fail2ban.tests import samplestestcase
|
|
||||||
|
|
||||||
if not regexps: # pragma: no cover
|
if not regexps: # pragma: no cover
|
||||||
tests = unittest.TestSuite()
|
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.Transmitter))
|
||||||
tests.addTest(unittest.makeSuite(servertestcase.JailTests))
|
tests.addTest(unittest.makeSuite(servertestcase.JailTests))
|
||||||
tests.addTest(unittest.makeSuite(servertestcase.RegexTests))
|
tests.addTest(unittest.makeSuite(servertestcase.RegexTests))
|
||||||
tests.addTest(unittest.makeSuite(actiontestcase.ExecuteAction))
|
tests.addTest(unittest.makeSuite(actiontestcase.CommandActionTest))
|
||||||
tests.addTest(unittest.makeSuite(actionstestcase.ExecuteActions))
|
tests.addTest(unittest.makeSuite(actionstestcase.ExecuteActions))
|
||||||
# FailManager
|
# FailManager
|
||||||
tests.addTest(unittest.makeSuite(failmanagertestcase.AddFailure))
|
tests.addTest(unittest.makeSuite(failmanagertestcase.AddFailure))
|
||||||
|
@ -207,30 +199,27 @@ def gatherTests(regexps=None, no_network=False):
|
||||||
|
|
||||||
# DateDetector
|
# DateDetector
|
||||||
tests.addTest(unittest.makeSuite(datedetectortestcase.DateDetectorTest))
|
tests.addTest(unittest.makeSuite(datedetectortestcase.DateDetectorTest))
|
||||||
if json:
|
# Filter Regex tests with sample logs
|
||||||
# Filter Regex tests with sample logs
|
tests.addTest(unittest.makeSuite(samplestestcase.FilterSamplesRegex))
|
||||||
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
|
# Extensive use-tests of different available filters backends
|
||||||
#
|
#
|
||||||
|
|
||||||
from fail2ban.server.filterpoll import FilterPoll
|
from ..server.filterpoll import FilterPoll
|
||||||
filters = [FilterPoll] # always available
|
filters = [FilterPoll] # always available
|
||||||
|
|
||||||
# Additional filters available only if external modules are available
|
# Additional filters available only if external modules are available
|
||||||
# yoh: Since I do not know better way for parametric tests
|
# yoh: Since I do not know better way for parametric tests
|
||||||
# with good old unittest
|
# with good old unittest
|
||||||
try:
|
try:
|
||||||
from fail2ban.server.filtergamin import FilterGamin
|
from ..server.filtergamin import FilterGamin
|
||||||
filters.append(FilterGamin)
|
filters.append(FilterGamin)
|
||||||
except Exception, e: # pragma: no cover
|
except Exception, e: # pragma: no cover
|
||||||
logSys.warning("Skipping gamin backend testing. Got exception '%s'" % e)
|
logSys.warning("Skipping gamin backend testing. Got exception '%s'" % e)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from fail2ban.server.filterpyinotify import FilterPyinotify
|
from ..server.filterpyinotify import FilterPyinotify
|
||||||
filters.append(FilterPyinotify)
|
filters.append(FilterPyinotify)
|
||||||
except Exception, e: # pragma: no cover
|
except Exception, e: # pragma: no cover
|
||||||
logSys.warning("I: Skipping pyinotify backend testing. Got exception '%s'" % e)
|
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(
|
tests.addTest(unittest.makeSuite(
|
||||||
filtertestcase.get_monitor_failures_testcase(Filter_)))
|
filtertestcase.get_monitor_failures_testcase(Filter_)))
|
||||||
try: # pragma: systemd no cover
|
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)))
|
tests.addTest(unittest.makeSuite(filtertestcase.get_monitor_failures_journal_testcase(FilterSystemd)))
|
||||||
except Exception, e: # pragma: no cover
|
except Exception, e: # pragma: no cover
|
||||||
logSys.warning("I: Skipping systemd backend testing. Got exception '%s'" % e)
|
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 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
|
.I filter.d/*.conf filter.d/*.local
|
||||||
.SH DESCRIPTION
|
.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
|
will try to use the following backends, in order: pyinotify, gamin, polling
|
||||||
.PP
|
.PP
|
||||||
.SS Actions
|
.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
|
.PP
|
||||||
.nf
|
.nf
|
||||||
[ssh-iptables-ipset]
|
[ssh-iptables-ipset]
|
||||||
enabled = true
|
enabled = true
|
||||||
action = sendmail[name=ssh, dest=john@example.com, actname=mail-john]
|
action = sendmail[name=ssh, dest=john@example.com, actname=mail-john]
|
||||||
sendmail[name=ssh, dest=paul@example.com, actname=mail-paul]
|
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
|
.fi
|
||||||
|
|
||||||
.SH "ACTION FILES"
|
.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 .
|
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.
|
The following commands can be present in the [Definition] section.
|
||||||
.TP
|
.TP
|
||||||
\fBactionstart\fR
|
\fBactionstart\fR
|
||||||
|
@ -148,12 +148,16 @@ command(s) that bans the IP address after \fBmaxretry\fR log lines matches withi
|
||||||
.TP
|
.TP
|
||||||
\fBactionunban\fR
|
\fBactionunban\fR
|
||||||
command(s) that unbans the IP address after \fBbantime\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
|
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).
|
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
|
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
|
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.
|
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
|
\fBipjailmatches\fR
|
||||||
As per \fBipmatches\fR, but matches are limited for the IP and for the current jail.
|
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
|
.SH FILTER FILES
|
||||||
|
|
||||||
Filter definitions are those in \fI/etc/fail2ban/filter.d/*.conf\fR and \fIfilter.d/*.local\fR.
|
Filter definitions are those in \fI/etc/fail2ban/filter.d/*.conf\fR and \fIfilter.d/*.local\fR.
|
||||||
|
|
3
setup.py
3
setup.py
|
@ -120,7 +120,8 @@ setup(
|
||||||
glob("config/filter.d/*.conf")
|
glob("config/filter.d/*.conf")
|
||||||
),
|
),
|
||||||
('/etc/fail2ban/action.d',
|
('/etc/fail2ban/action.d',
|
||||||
glob("config/action.d/*.conf")
|
glob("config/action.d/*.conf") +
|
||||||
|
glob("config/action.d/*.py")
|
||||||
),
|
),
|
||||||
('/etc/fail2ban/fail2ban.d',
|
('/etc/fail2ban/fail2ban.d',
|
||||||
''
|
''
|
||||||
|
|
Loading…
Reference in New Issue