diff --git a/.gitignore b/.gitignore index c2e979e5..76a33e60 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ htmlcov .coverage *.orig *.rej +*.bak +__pycache__ diff --git a/.travis.yml b/.travis.yml index 2d091754..41eeca27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,17 +2,20 @@ # travis-ci.org definition for Fail2Ban build language: python python: - - "2.5" - "2.6" - "2.7" + - "3.2" + - "3.3" + - "pypy" before_install: - - sudo apt-get update -qq + - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then sudo apt-get update -qq; fi install: - pip install pyinotify - - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then sudo apt-get install -qq python-gamin; fi - - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then pip install -q coveralls; fi + - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then sudo apt-get install -qq python-gamin; cp /usr/share/pyshared/gamin.py /usr/lib/pyshared/python2.7/_gamin.so $VIRTUAL_ENV/lib/python2.7/site-packages/; fi + - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then cd ..; pip install -q coveralls; cd -; fi script: - - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then export PYTHONPATH="$PYTHONPATH:/usr/share/pyshared:/usr/lib/pyshared/python2.7"; fi - - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then coverage run --rcfile=.travis_coveragerc fail2ban-testcases; else python ./fail2ban-testcases; fi + - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then coverage run --rcfile=.travis_coveragerc setup.py test; else python setup.py test; fi after_success: +# Coverage config file must be .coveragerc for coveralls + - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then cp -v .travis_coveragerc .coveragerc; fi - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then coveralls; fi diff --git a/.travis_coveragerc b/.travis_coveragerc index ac4a15d5..49fc3134 100644 --- a/.travis_coveragerc +++ b/.travis_coveragerc @@ -4,3 +4,4 @@ branch = True omit = /usr/* /home/travis/virtualenv/* + fail2ban/server/filtersystemd.py diff --git a/ChangeLog b/ChangeLog index e901d9af..e2ac820d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,12 +4,197 @@ |_| \__,_|_|_/___|_.__/\__,_|_||_| ================================================================================ -Fail2Ban (version 0.8.11) 2013/11/13 +Fail2Ban (version 0.9.0) 2014/03/14 ================================================================================ -ver. 0.8.11 (2013/11/13) - loves-unittests-and-tight-DoS-free-filter-regexes +ver. 0.9.0 (2014/03/14 - beta +---------- + +Carries all fixes, features and enhancements from 0.8.13 (unreleased) with +major changes. + +The minimum supported python version is now 2.6. If you have python-2.4 or 2.5 +you can use the 0.8.12 version of fail2ban. + +Please take note of release notes: +https://github.com/fail2ban/fail2ban/releases/tag/0.9.0 + +Please test your configuration before relying on it. + +Nearly all development is thanks to Steven Hiscocks (THANKS!), merging, +testcases and timezone support from Daniel Black, and code-review and minor +additions from Yaroslav Halchenko. + +- Refactoring (IMPORTANT -- Please review your setup and configuration): + * [..bddbf1e] jail.conf was heavily refactored and now is similar + to how it looked on Debian systems: + - default action could be configured once for all jails + - jails definitions only provide customizations (port, logpath) + - no need to specify 'filter' if name matches jail name + * [..5aef036] Core functionality moved into fail2ban/ module. + Closes gh-26 + - tests included in module to aid testing and debugging + * Added fail2ban persistent database + - default location at /var/lib/fail2ban/fail2ban.sqlite3 + - allows active bans to be reinstated on restart + - log files read from last position after restart + * Added systemd journal backend + - Dependency on python-systemd + - New "journalmatch" option added to filter configs files + - New "systemd-journal" option added to fail2ban-regex + * Added python3 support + * Support %z (Timezone offset) and %f (sub-seconds) support for + datedetector. Enhanced existing date/time have been updated patterns to + support these. ISO8601 now defaults to localtime unless specified otherwise. + Some filters have been change as required to capture these elements in the + right timezone correctly. + * Log levels are now set by Syslog style strings e.g. DEBUG, ERROR. + - Log level INFO is now more verbose + * Optionally can read log files starting from "head" or "tail". + - See "logpath" option in jail.conf(5) man page. + * Can now set log encoding for files per jail. + - Default uses systemd locale. + +- New features: + * [..c7ae460] Multiline failregex. Close gh-54 + * [8af32ed] Guacamole filter and support for Apache Tomcat date + format + * [..b6059f4] 'timeout' option for actions Close gh-60 and Debian bug + #410077. Also it would now capture and include stdout and stderr + into logging messages in case of error or at DEBUG loglevel. + * Added action xarf-login-attack to report formatted attack messages + according to the XARF standard (v0.2). Close gh-105 + * Support PyPy + * Add filter for apache-botsearch + * Add filter for kerio. Thanks Tony Lawrence for blog of regexs and + providing samples. Close gh-120 + * Filter for stunnel + * Filter for Counter Strike 1.6. Thanks to onorua for logs. + Close gh-347 + * Filter for squirrelmail. Close gh-261 + * Filter for tine20. Close gh-583 + * Custom date formats (strptime) can now be set in filters and jail.conf + * Python based actions can now be created. + - SMTP action for sending emails on jail start, stop and ban. + * Added action to use badips.com reporting and blacklist + - Requires Python 2.7+ + +- Enhancements + * Fail2ban-regex - don't accumulate lines if not printing them. + add options to suppress output of missed/ignored lines. Close gh-644 + * Asterisk now supports syslog format + * Jail names increased to 26 characters and iptables prefix reduced + from fail2ban- to f2b- as suggested by buanzo in gh-462. + * Multiline filter for sendmail-spam. Close gh-418 + * Multiline regex for Disconnecting: Too many authentication failures for + root [preauth]\nConnection closed by 6X.XXX.XXX.XXX [preauth] + * Multiline regex for Disconnecting: Connection from 61.XX.XX.XX port + 51353\nToo many authentication failures for root [preauth]. Thanks + Helmut Grohne. Close gh-457 + * Replacing use of deprecated API (.warning, .assertEqual, etc) + * [..a648cc2] Filters can have options now too which are substituted into + failregex / ignoreregex + * [..e019ab7] Multiple instances of the same action are allowed in the + same jail -- use actname option to disambiguate. + * Add honeypot email address to exim-spam filter as argument + * Properties and methods of actions accessible from fail2ban-client + - Use of properties replaces command actions "cinfo" interface + + +ver. 0.8.13 (2014/XX/XXX) - maintenance-only-from-now-on ----------- +- Fixes: + - action firewallcmd-ipset had non-working actioncheck. Removed. + redhat bug #1046816. + - filter pureftpd - added _daemon which got removed. Added + +- New Features: + - filter nagios - detects unauthorized access to the nrpe daemon (Ivo Truxa) + - filter sendmail-{auth,reject} (jserrachinha and cepheid666 and fab23). + +- Enhancements: + - filter pureftpd - added all translations of "Authentication failed for + user" + - filter dovecot - lip= was optional and extended TLS errors can occur. + Thanks Noel Butler. + +ver. 0.8.12 (2014/01/22) - things-can-only-get-better +---------- + +- IMPORTANT incompatible changes: + - Rename firewall-cmd-direct-new to firewallcmd-new to fit within jail name + name length. As per gh-395 + - mysqld-syslog-iptables jailname was too long. Renamed to mysqld-syslog. + Part of gh-447. + +- Fixes: + - allow for ",milliseconds" in the custom date format of proftpd.log + - allow for ", referer ..." in apache-* filter for apache error logs. + - allow for spaces at the beginning of kernel messages. Closes gh-448 + - recidive jail to block all protocols. Closes gh-440. Thanks Ioan Indreias + - smtps not a IANA standard and has been removed from Arch. Replaced with + 465. Thanks Stefan. Closes gh-447 + - add 'flushlogs' command to allow logrotation without clobbering logtarget + settings. Closes gh-458, Debian bug #697333, Redhat bug #891798. + - complain action - ensure where not matching other IPs in log sample. + Closes gh-467 + - Fix firewall-cmd actioncheck - patch from Adam Tkac. Redhat Bug #979622 + - Fix apache-common for apache-2.4 log file format. Thanks Mark White. + Closes gh-516 + - Asynchat changed to use push method which verifys whether all data was + send. This ensures that all data is sent before closing the connection. + - Removed unnecessary reference to as yet undeclared $jail_name when checking + a specific jail in nagios script. + - Filter dovecot reordered session and TLS items in regex with wider scope + for session characters. Thanks Ivo Truxa. Closes gh-586 + - A single bad failregex or command syntax in configuration files won't stop + fail2ban from starting. Thanks Tomasz Ciolek. Closes gh-585. + +- Enhancements: + - long names on jails documented based on iptables limit of 30 less + len("fail2ban-"). + - remove indentation of name and loglevel while logging to SYSLOG to + resolve syslog(-ng) parsing problems. Closes Debian bug #730202. + - updated check_fail2ban to return performance data for all jails. + - filter apache-noscript now includes php cgi scripts. + Thanks dani. Closes gh-503 + - 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 + - Added to sshd filter expression for "Received disconnect from : 3: + ...: Auth fail". Thanks Marcel Dopita. Closes gh-289 + - loglines now also report "[PID]" after the name portion + - Added filter.d/ejabberd-auth + - Improved ACL-handling for Asterisk + - loglines now also report "[PID]" after the name portion + - Added improper command pipelining to postfix filter. + +- New Features: + + - filter.d/solid-pop3d -- added thanks to Jacques Lav!gnotte on mailinglist. + - Add filter for apache-modsecurity. + - filter.d/nsd.conf -- also amended Unix date template to match nsd format + - Added openwebmail filter thanks Ivo Truxa. Closes gh-543 + - Added filter for freeswitch. Thanks Jim and editors and authors of + http://wiki.freeswitch.org/wiki/Fail2ban + - Added groupoffice filter thanks to logs from Merijn Schering. + Closes gh-566 + - Added filter for horde + - Added filter for squid. Thanks Roman Gelfand. + - Added filter for ejabberd-auth. + - Added filter.d/openwebmail filter thanks Ivo Truxa. Closes gh-543 + - Added filter.d/groupoffice filter thanks to logs from Merijn Schering. + Closes gh-566 + - Added action.d/badips. Thanks to Amy for making a nice API. + - Added firewallcmd-ipset action. + - Added ufw action. Thanks Guilhem Lettron. lp-#701522 + - Added blocklist_de action. + + +ver. 0.8.11 (2013/11/13) - loves-unittests-and-tight-DoS-free-filter-regexes +---------- + In light of CVE-2013-2178 that triggered our last release we have put a significant effort into tightening all of the regexs of our filters to avoid another similar vulnerability. All filters have been updated @@ -805,7 +990,7 @@ ver. 0.5.4 (2005/09/13) - beta * Fixed errata in config/gentoo-confd * Introduced findtime configuration variable to control the lifetime of caught "failed" log entries - + ver. 0.5.3 (2005/09/08) - beta ---------- - Fixed a bug when overriding "maxfailures" or "bantime". Thanks to Yaroslav diff --git a/DEVELOP b/DEVELOP index d776d8f7..e81bca7e 100644 --- a/DEVELOP +++ b/DEVELOP @@ -34,472 +34,14 @@ When submitting pull requests on GitHub we ask you to: * Include a change to the relevant section of the ChangeLog; and * Include yourself in THANKS if not already there. -Filters -======= - -Filters are tricky. They need to: -* work with a variety of the versions of the software that generates the logs; -* work with the range of logging configuration options available in the - software; -* work with multiple operating systems; -* not make assumptions about the log format in excess of the software - (e.g. do not assume a username doesn't contain spaces and use \S+ unless - you've checked the source code); -* account for how future versions of the software will log messages - (e.g. guess what would happen to the log message if different authentication - types are added); -* not be susceptible to DoS vulnerabilities (see Filter Security below); and -* match intended log lines only. - -Please follow the steps from Filter Test Cases to Developing Filter Regular -Expressions and submit a GitHub pull request (PR) afterwards. If you get stuck, -you can push your unfinished changes and still submit a PR -- describe -what you have done, what is the hurdle, and we'll attempt to help (PR -will be automagically updated with future commits you would push to -complete it). - -Filter test cases ------------------ - -Purpose: - -Start by finding the log messages that the application generates related to -some form of authentication failure. If you are adding to an existing filter -think about whether the log messages are of a similar importance and purpose -to the existing filter. If you were a user of Fail2Ban, and did a package -update of Fail2Ban that started matching new log messages, would anything -unexpected happen? Would the bantime/findtime for the jail be appropriate for -the new log messages? If it doesn't, perhaps it needs to be in a separate -filter definition, for example like exim filter aims at authentication failures -and exim-spam at log messages related to spam. - -Even if it is a new filter you may consider separating the log messages into -different filters based on purpose. - -Cause: - -Are some of the log lines a result of the same action? For example, is a PAM -failure log message, followed by an application specific failure message the -result of the same user/script action? If you add regular expressions for -both you would end up with two failures for a single action. -Therefore, select the most appropriate log message and document the other log -message) with a test case not to match it and a description as to why you chose -one over another. - -With the selected log lines consider what action has caused those log -messages and whether they could have been generated by accident? Could -the log message be occurring due to the first step towards the application -asking for authentication? Could the log messages occur often? If some of -these are true make a note of this in the jail.conf example that you provide. - -Samples: - -It is important to include log file samples so any future change in the regular -expression will still work with the log lines you have identified. - -The sample log messages are provided in a file under testcases/files/logs/ -named identically as the corresponding filter (but without .conf extension). -Each log line should be preceded by a line with failJSON metadata (so the logs -lines are tested in the test suite) directly above the log line. If there is -any specific information about the log message, such as version or an -application configuration option that is needed for the message to occur, -include this in a comment (line beginning with #) above the failJSON metadata. - -Log samples should include only one, definitely not more than 3, examples of -log messages of the same form. If log messages are different in different -versions of the application log messages that show this are encouraged. - -Also attempt to inject an IP into the application (e.g. by specifying -it as a username) so that Fail2Ban possibly detects the IP -from user input rather than the true origin. See the Filter Security section -and the top example in testcases/files/logs/apache-auth as to how to do this. -One you have discovered that this is possible, correct the regex so it doesn't -match and provide this as a test case with "match": false (see failJSON below). - -If the mechanism to create the log message isn't obvious provide a -configuration and/or sample scripts testcases/files/config/{filtername} and -reference these in the comments above the log line. - -FailJSON metadata: - -A failJSON metadata is a comment immediately above the log message. It will -look like: - -# failJSON: { "time": "2013-06-10T10:10:59", "match": true , "host": "93.184.216.119" } - -Time should match the time of the log message. It is in a specific format of -Year-Month-Day'T'Hour:minute:Second. If your log message does not include a -year, like the example below, the year should be listed as 2005, if before Sun -Aug 14 10am UTC, and 2004 if afterwards. Here is an example failJSON -line preceding a sample log line: - -# failJSON: { "time": "2005-03-24T15:25:51", "match": true , "host": "198.51.100.87" } -Mar 24 15:25:51 buffalo1 dropbear[4092]: bad password attempt for 'root' from 198.51.100.87:5543 - -The "host" in failJSON should contain the IP or domain that should be blocked. - -For long lines that you do not want to be matched (e.g. from log injection -attacks) and any log lines to be excluded (see "Cause" section above), set -"match": false in the failJSON and describe the reason in the comment above. - -After developing regexes, the following command will test all failJSON metadata -against the log lines in all sample log files - -./fail2ban-testcases testSampleRegex - -Developing Filter Regular Expressions -------------------------------------- - -Date/Time: - -At the moment, Fail2Ban depends on log lines to have time stamps. That is why -before starting to develop failregex, check if your log line format known to -Fail2Ban. Copy the time component from the log line and append an IP address to -test with following command: - -./fail2ban-regex "2013-09-19 02:46:12 1.2.3.4" "" - -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 ' error 1.2.3.4 is evil' then you need to match -the < at the start so regex should be similar to '^<> is evil$' using - 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 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 part, they have the -ability to deny any host they choose. - -So the 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 - -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 - -Here the .* will match until the end of the string. Then realise it has more to -match, i.e. "from " and go back until it find this. Then it will ban -1.2.3.4 correctly. Since the is always at the end, end the regex with a $. - - ^Invalid command .* from $ - -Note if we'd just had the expression: - - ^Invalid command \S+ from $ - -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 []] 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 ( 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 ( 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 ( 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 " 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 ( port \d*)?( ssh\d+)?(: ruser .*)?$' - - 1) [1] ^ Failed \S+ for .*? from ( 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)(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: , 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 . - -4. Application generates two identical log messages with different meanings - -If the application generates the following two messages under different -circumstances: - - client : authentication failed - client : authentication failed - - -Then it's obvious that a regex of "^client : authentication -failed$" will still cause problems if the user can trigger the second -log message with a of 123.1.1.1. - -Here there's nothing to do except request/change the application so it logs -messages differently. - +If you are developing filters see the FILTERS file for documentation. Code Testing ============ -Existing tests can be run by executing `fail2ban-testcases`. This has options -like --log-level that will probably be useful. `fail2ban-testcases --help` for -full options. +Existing tests can be run by executing `bin/fail2ban-testcases`. It has +options like --log-level that will probably be useful. Run +`bin/fail2ban-testcases --help` for the full list of options. Test cases should cover all usual cases, all exception cases and all inside / outside boundary conditions. @@ -512,7 +54,7 @@ Install the package python-coverage to visualise your test coverage. Run the following (note: on Debian-based systems, the script is called `python-coverage`): -coverage run fail2ban-testcases +coverage run bin/fail2ban-testcases coverage html Then look at htmlcov/index.html and see how much coverage your test cases @@ -726,148 +268,3 @@ action.py Takes care about executing start/check/ban/unban/stop commands - -Releasing -========= - -# Check distribution patches and see if they can be included - - * https://apps.fedoraproject.org/packages/fail2ban/sources - * http://sources.gentoo.org/cgi-bin/viewvc.cgi/gentoo-x86/net-analyzer/fail2ban/ - * http://svnweb.freebsd.org/ports/head/security/py-fail2ban/ - * https://build.opensuse.org/package/show?package=fail2ban&project=openSUSE%3AFactory - * http://sophie.zarb.org/sources/fail2ban (Mageia) - * https://trac.macports.org/browser/trunk/dports/security/fail2ban - -# Check distribution outstanding bugs - - * https://github.com/fail2ban/fail2ban/issues?sort=updated&state=open - * http://bugs.debian.org/cgi-bin/pkgreport.cgi?dist=unstable;package=fail2ban - * http://bugs.sabayon.org/buglist.cgi?quicksearch=net-analyzer%2Ffail2ban - * https://bugs.gentoo.org/buglist.cgi?query_format=advanced&short_desc=fail2ban&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=IN_PROGRESS&short_desc_type=allwords - * https://bugzilla.redhat.com/buglist.cgi?query_format=advanced&bug_status=NEW&bug_status=ASSIGNED&component=fail2ban&classification=Red%20Hat&classification=Fedora - * http://www.freebsd.org/cgi/query-pr-summary.cgi?text=fail2ban - -# Make sure the tests pass - - ./fail2ban-testcases-all - -# Ensure the version is correct - - in: - * ./common/version.py - * top of ChangeLog - * README.md - -# Ensure the MANIFEST is complete - -Run: - - python setup.py sdist - -Look for errors like: - 'testcases/files/logs/mysqld.log' not a regular file -- skipping - -Which indicates that testcases/files/logs/mysqld.log has been moved or is a directory - - tar -C /tmp -jxf dist/fail2ban-0.8.11.tar.bz2 - -# clean up current direcory - - diff -rul --exclude \*.pyc . /tmp/fail2ban-0.8.11/ - - # Only differences should be files that you don't want distributed. - -# Ensure the tests work from the tarball - - cd /tmp/fail2ban-0.8.11/ && ./fail2ban-testcases-all - -# Add/finalize the corresponding entry in the ChangeLog - - To generate a list of committers use e.g. - - git shortlog -sn 0.8.10.. | sed -e 's,^[ 0-9\t]*,,g' | tr '\n' '\|' | sed -e 's:|:, :g' - - Ensure the top of the ChangeLog has the right version and current date. - - Ensure the top entry of the ChangeLog has the right version and current date. - -# Update man pages - - (cd man ; ./generate-man ) - git commit -m 'DOC/ENH: update man pages for release' man/* - -# Prepare source and rpm binary distributions - - python setup.py sdist - python setup.py bdist_rpm - python setup.py upload - -# Provide a release sample to distributors - - * Debian: Yaroslav Halchenko - http://packages.qa.debian.org/f/fail2ban.html - * FreeBSD: Christoph Theis theis@gmx.at>, Nick Hilliard - http://svnweb.freebsd.org/ports/head/security/py-fail2ban/Makefile?view=markup - http://www.freebsd.org/cgi/query-pr-summary.cgi?text=fail2ban - * Fedora: Axel Thimm - https://apps.fedoraproject.org/packages/fail2ban - http://pkgs.fedoraproject.org/cgit/fail2ban.git - https://admin.fedoraproject.org/pkgdb/acls/bugs/fail2ban - * Gentoo: netmon@gentoo.org - http://sources.gentoo.org/cgi-bin/viewvc.cgi/gentoo-x86/net-analyzer/fail2ban/metadata.xml?view=markup - https://bugs.gentoo.org/buglist.cgi?quicksearch=fail2ban - * openSUSE: Stephan Kulow - https://build.opensuse.org/package/show/openSUSE:Factory/fail2ban - * Mac Ports: @Malbrouck on github (gh-49) - https://trac.macports.org/browser/trunk/dports/security/fail2ban/Portfile - * Mageia: - https://bugs.mageia.org/buglist.cgi?quicksearch=fail2ban - An potentially to the fail2ban-users directory. - -# Wait for feedback from distributors - -# Prepare a release notice https://github.com/fail2ban/fail2ban/releases/new - - Upload the source/binaries from the dist directory and tag the release using the URL - -# Upload source/binaries to sourceforge http://sourceforge.net/projects/fail2ban/ - -# Run the following and update the wiki with output: - python -c 'import common.protocol; common.protocol.printWiki()' - - page: http://www.fail2ban.org/wiki/index.php/Commands - -* Update: - http://www.fail2ban.org/wiki/index.php/Downloads - http://www.fail2ban.org/wiki/index.php/ChangeLog - http://www.fail2ban.org/wiki/index.php/Requirements (Check requirement) - http://www.fail2ban.org/wiki/index.php/Main_Page (Add to News) - http://www.fail2ban.org/wiki/index.php/Features - -* See if any filters are upgraded: - http://www.fail2ban.org/wiki/index.php/Special:AllPages - -# Email users and development list of release - -# notify distributors - -Post Release -============ - -Add the following to the top of the ChangeLog - -ver. 0.8.12 (2013/XX/XXX) - wanna-be-released ------------ - -- Fixes: - -- New Features: - -- Enhancements: - -Alter the git shortlog command in the previous section to refer to the just -released version. - -and adjust common/version.py to carry .dev suffix to signal -a version under development. diff --git a/FILTERS b/FILTERS new file mode 100644 index 00000000..fd441e58 --- /dev/null +++ b/FILTERS @@ -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" "" + +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 ' error 1.2.3.4 is evil' then you need to match +the < at the start so regex should be similar to '^<> is evil$' using + 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 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 part, they have the +ability to deny any host they choose. + +So the 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 + +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 + +Here the .* will match until the end of the string. Then realise it has more to +match, i.e. "from " and go back until it find this. Then it will ban +1.2.3.4 correctly. Since the is always at the end, end the regex with a $. + + ^Invalid command .* from $ + +Note if we'd just had the expression: + + ^Invalid command \S+ from $ + +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 []] 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 ( 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 ( 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 ( 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 " 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 ( port \d*)?( ssh\d+)?(: ruser .*)?$' + + 1) [1] ^ Failed \S+ for .*? from ( 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)(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: , 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 . + +4. Application generates two identical log messages with different meanings + +If the application generates the following two messages under different +circumstances: + + client : authentication failed + client : authentication failed + + +Then it's obvious that a regex of "^client : authentication +failed$" will still cause problems if the user can trigger the second +log message with a of 123.1.1.1. + +Here there's nothing to do except request/change the application so it logs +messages differently. + + diff --git a/MANIFEST b/MANIFEST index 0e0eb327..9987adaf 100644 --- a/MANIFEST +++ b/MANIFEST @@ -5,149 +5,203 @@ TODO THANKS COPYING DEVELOP -fail2ban-client -fail2ban-server -fail2ban-testcases -fail2ban-regex +FILTERS +fail2ban-2to3 fail2ban-testcases-all +fail2ban-testcases-all-python3 +bin/fail2ban-client +bin/fail2ban-server +bin/fail2ban-testcases +bin/fail2ban-regex +doc/run-rootless.txt +fail2ban/client/configreader.py +fail2ban/client/configparserinc.py +fail2ban/client/jailreader.py +fail2ban/client/fail2banreader.py +fail2ban/client/jailsreader.py +fail2ban/client/beautifier.py +fail2ban/client/filterreader.py +fail2ban/client/actionreader.py +fail2ban/client/__init__.py +fail2ban/client/configurator.py +fail2ban/client/csocket.py +fail2ban/server/asyncserver.py +fail2ban/server/database.py +fail2ban/server/filter.py +fail2ban/server/filterpyinotify.py +fail2ban/server/filtergamin.py +fail2ban/server/filterpoll.py +fail2ban/server/filtersystemd.py +fail2ban/server/iso8601.py +fail2ban/server/server.py +fail2ban/server/actions.py +fail2ban/server/faildata.py +fail2ban/server/failmanager.py +fail2ban/server/datedetector.py +fail2ban/server/jailthread.py +fail2ban/server/transmitter.py +fail2ban/server/action.py +fail2ban/server/ticket.py +fail2ban/server/jail.py +fail2ban/server/jails.py +fail2ban/server/__init__.py +fail2ban/server/banmanager.py +fail2ban/server/datetemplate.py +fail2ban/server/mytime.py +fail2ban/server/failregex.py +fail2ban/server/database.py +fail2ban/tests/banmanagertestcase.py +fail2ban/tests/failmanagertestcase.py +fail2ban/tests/clientreadertestcase.py +fail2ban/tests/filtertestcase.py +fail2ban/tests/__init__.py +fail2ban/tests/dummyjail.py +fail2ban/tests/samplestestcase.py +fail2ban/tests/datedetectortestcase.py +fail2ban/tests/actiontestcase.py +fail2ban/tests/servertestcase.py +fail2ban/tests/sockettestcase.py +fail2ban/tests/utils.py +fail2ban/tests/misctestcase.py +fail2ban/tests/databasetestcase.py +fail2ban/tests/config/jail.conf +fail2ban/tests/config/fail2ban.conf +fail2ban/tests/config/paths-common.conf +fail2ban/tests/config/paths-freebsd.conf +fail2ban/tests/config/paths-osx.conf +fail2ban/tests/config/paths-debian.conf +fail2ban/tests/config/filter.d/simple.conf +fail2ban/tests/config/action.d/brokenaction.conf +fail2ban/tests/files/config/apache-auth/digest/.htaccess +fail2ban/tests/files/config/apache-auth/digest/.htpasswd +fail2ban/tests/files/config/apache-auth/digest_time/.htaccess +fail2ban/tests/files/config/apache-auth/digest_time/.htpasswd +fail2ban/tests/files/config/apache-auth/basic/authz_owner/.htaccess +fail2ban/tests/files/config/apache-auth/basic/authz_owner/cant_get_me.html +fail2ban/tests/files/config/apache-auth/basic/authz_owner/.htpasswd +fail2ban/tests/files/config/apache-auth/basic/file/.htaccess +fail2ban/tests/files/config/apache-auth/basic/file/.htpasswd +fail2ban/tests/files/config/apache-auth/digest.py +fail2ban/tests/files/config/apache-auth/digest_wrongrelm/.htaccess +fail2ban/tests/files/config/apache-auth/digest_wrongrelm/.htpasswd +fail2ban/tests/files/config/apache-auth/digest_anon/.htaccess +fail2ban/tests/files/config/apache-auth/digest_anon/.htpasswd +fail2ban/tests/files/config/apache-auth/README +fail2ban/tests/files/config/apache-auth/noentry/.htaccess +fail2ban/tests/files/database_v1.db +fail2ban/tests/files/ignorecommand.py +fail2ban/tests/files/filter.d/substition.conf +fail2ban/tests/files/filter.d/testcase-common.conf +fail2ban/tests/files/filter.d/testcase01.conf +fail2ban/tests/files/testcase01.log +fail2ban/tests/files/testcase02.log +fail2ban/tests/files/testcase03.log +fail2ban/tests/files/testcase04.log +fail2ban/tests/files/testcase-usedns.log +fail2ban/tests/files/testcase-journal.log +fail2ban/tests/files/testcase-multiline.log +fail2ban/tests/files/logs/bsd/syslog-plain.txt +fail2ban/tests/files/logs/bsd/syslog-v.txt +fail2ban/tests/files/logs/bsd/syslog-vv.txt +fail2ban/tests/files/logs/3proxy +fail2ban/tests/files/logs/apache-auth +fail2ban/tests/files/logs/apache-badbots +fail2ban/tests/files/logs/apache-botscripts +fail2ban/tests/files/logs/apache-modsecurity +fail2ban/tests/files/logs/apache-nohome +fail2ban/tests/files/logs/apache-noscript +fail2ban/tests/files/logs/apache-overflows +fail2ban/tests/files/logs/assp +fail2ban/tests/files/logs/asterisk +fail2ban/tests/files/logs/counter-strike +fail2ban/tests/files/logs/courier-auth +fail2ban/tests/files/logs/courier-smtp +fail2ban/tests/files/logs/cyrus-imap +fail2ban/tests/files/logs/dovecot +fail2ban/tests/files/logs/dropbear +fail2ban/tests/files/logs/ejabberd-auth +fail2ban/tests/files/logs/exim +fail2ban/tests/files/logs/exim-spam +fail2ban/tests/files/logs/freeswitch +fail2ban/tests/files/logs/groupoffice +fail2ban/tests/files/logs/gssftpd +fail2ban/tests/files/logs/guacamole +fail2ban/tests/files/logs/kerio +fail2ban/tests/files/logs/lighttpd-auth +fail2ban/tests/files/logs/mysqld-auth +fail2ban/tests/files/logs/nsd +fail2ban/tests/files/logs/perdition +fail2ban/tests/files/logs/php-url-fopen +fail2ban/tests/files/logs/postfix-sasl +fail2ban/tests/files/logs/named-refused +fail2ban/tests/files/logs/nginx-http-auth +fail2ban/tests/files/logs/pam-generic +fail2ban/tests/files/logs/postfix +fail2ban/tests/files/logs/proftpd +fail2ban/tests/files/logs/pure-ftpd +fail2ban/tests/files/logs/qmail +fail2ban/tests/files/logs/recidive +fail2ban/tests/files/logs/roundcube-auth +fail2ban/tests/files/logs/selinux-ssh +fail2ban/tests/files/logs/sendmail-spam +fail2ban/tests/files/logs/sieve +fail2ban/tests/files/logs/squid +fail2ban/tests/files/logs/stunnel +fail2ban/tests/files/logs/suhosin +fail2ban/tests/files/logs/sogo-auth +fail2ban/tests/files/logs/solid-pop3d +fail2ban/tests/files/logs/sshd +fail2ban/tests/files/logs/sshd-ddos +fail2ban/tests/files/logs/vsftpd +fail2ban/tests/files/logs/webmin-auth +fail2ban/tests/files/logs/wuftpd +fail2ban/tests/files/logs/uwimap-auth +fail2ban/tests/files/logs/xinetd-fail +fail2ban/tests/config/jail.conf +fail2ban/tests/config/fail2ban.conf +fail2ban/tests/config/filter.d/simple.conf +fail2ban/tests/config/action.d/brokenaction.conf +setup.py +setup.cfg +fail2ban/__init__.py +fail2ban/exceptions.py +fail2ban/helpers.py +fail2ban/version.py +fail2ban/protocol.py setup.py setup.cfg kill-server -client/configreader.py -client/configparserinc.py -client/jailreader.py -client/fail2banreader.py -client/jailsreader.py -client/beautifier.py -client/filterreader.py -client/actionreader.py -client/__init__.py -client/configurator.py -client/csocket.py -server/asyncserver.py -server/filter.py -server/filterpyinotify.py -server/filtergamin.py -server/filterpoll.py -server/iso8601.py -server/server.py -server/actions.py -server/faildata.py -server/failmanager.py -server/datedetector.py -server/jailthread.py -server/transmitter.py -server/action.py -server/ticket.py -server/jail.py -server/jails.py -server/__init__.py -server/banmanager.py -server/datetemplate.py -server/mytime.py -server/failregex.py -testcases/actionstestcase.py -testcases/dummyjail.py -testcases/files/testcase-usedns.log -testcases/files/logs/bsd/syslog-plain.txt -testcases/files/logs/bsd/syslog-v.txt -testcases/files/logs/bsd/syslog-vv.txt -testcases/files/logs/apache-overflows -testcases/files/logs/assp -testcases/files/logs/asterisk -testcases/files/logs/dovecot -testcases/files/logs/exim -testcases/files/logs/suhosin -testcases/files/logs/mysqld-auth -testcases/files/logs/named-refused -testcases/files/logs/nginx-http-auth -testcases/files/logs/pam-generic -testcases/files/logs/postfix -testcases/files/logs/proftpd -testcases/files/logs/pure-ftpd -testcases/files/logs/roundcube-auth -testcases/files/logs/postfix-sasl -testcases/files/logs/sogo-auth -testcases/files/logs/sshd -testcases/files/logs/sshd-ddos -testcases/files/logs/vsftpd -testcases/files/logs/webmin-auth -testcases/files/logs/wuftpd -testcases/files/logs/3proxy -testcases/files/logs/apache-auth -testcases/files/logs/apache-badbots -testcases/files/logs/apache-nohome -testcases/files/logs/apache-noscript -testcases/files/logs/courierlogin -testcases/files/logs/couriersmtp -testcases/files/logs/cyrus-imap -testcases/files/logs/dropbear -testcases/files/logs/exim-spam -testcases/files/logs/gssftpd -testcases/files/logs/lighttpd-auth -testcases/files/logs/mysqld-auth -testcases/files/logs/perdition -testcases/files/logs/php-url-fopen -testcases/files/logs/qmail -testcases/files/logs/recidive -testcases/files/logs/sieve -testcases/files/logs/selinux-ssh -testcases/files/logs/suhosin -testcases/files/logs/uwimap-auth -testcases/files/logs/wuftpd -testcases/files/logs/xinetd-fail -testcases/files/config/apache-auth/digest/.htaccess -testcases/files/config/apache-auth/digest/.htpasswd -testcases/files/config/apache-auth/digest_time/.htaccess -testcases/files/config/apache-auth/digest_time/.htpasswd -testcases/files/config/apache-auth/basic/authz_owner/.htaccess -testcases/files/config/apache-auth/basic/authz_owner/cant_get_me.html -testcases/files/config/apache-auth/basic/authz_owner/.htpasswd -testcases/files/config/apache-auth/basic/file/.htaccess -testcases/files/config/apache-auth/basic/file/.htpasswd -testcases/files/config/apache-auth/digest.py -testcases/files/config/apache-auth/digest_wrongrelm/.htaccess -testcases/files/config/apache-auth/digest_wrongrelm/.htpasswd -testcases/files/config/apache-auth/digest_anon/.htaccess -testcases/files/config/apache-auth/digest_anon/.htpasswd -testcases/files/config/apache-auth/README -testcases/files/config/apache-auth/noentry/.htaccess -testcases/samplestestcase.py -testcases/banmanagertestcase.py -testcases/failmanagertestcase.py -testcases/clientreadertestcase.py -testcases/filtertestcase.py -testcases/__init__.py -testcases/datedetectortestcase.py -testcases/actiontestcase.py -testcases/servertestcase.py -testcases/sockettestcase.py -testcases/files/testcase01.log -testcases/files/testcase02.log -testcases/files/testcase03.log -testcases/files/testcase04.log -testcases/misctestcase.py -testcases/utils.py -common/__init__.py -common/exceptions.py -common/helpers.py -common/version.py -common/protocol.py config/jail.conf +config/fail2ban.conf config/filter.d/common.conf config/filter.d/apache-auth.conf config/filter.d/apache-badbots.conf +config/filter.d/apache-botsearch.conf +config/filter.d/apache-modsecurity.conf config/filter.d/apache-nohome.conf config/filter.d/apache-noscript.conf config/filter.d/apache-overflows.conf config/filter.d/nginx-http-auth.conf -config/filter.d/courierlogin.conf -config/filter.d/couriersmtp.conf +config/filter.d/counter-strike.conf +config/filter.d/courier-auth.conf +config/filter.d/courier-smtp.conf config/filter.d/cyrus-imap.conf +config/filter.d/ejabberd-auth.conf config/filter.d/exim.conf +config/filter.d/freeswitch.conf config/filter.d/gssftpd.conf +config/filter.d/kerio.conf +config/filter.d/horde.conf config/filter.d/suhosin.conf config/filter.d/named-refused.conf +config/filter.d/nsd.conf +config/filter.d/openwebmail.conf +config/filter.d/pam-generic.conf +config/filter.d/php-url-fopen.conf +config/filter.d/postfix-sasl.conf +config/filter.d/pam-generic.conf +config/filter.d/php-url-fopen.conf +config/filter.d/postfix-sasl.conf config/filter.d/postfix.conf config/filter.d/proftpd.conf config/filter.d/pure-ftpd.conf @@ -155,9 +209,14 @@ 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/sendmail-auth.conf +config/filter.d/sendmail-reject.conf config/filter.d/sieve.conf +config/filter.d/solid-pop3d.conf +config/filter.d/squid.conf config/filter.d/sshd.conf config/filter.d/sshd-ddos.conf +config/filter.d/stunnel.conf config/filter.d/vsftpd.conf config/filter.d/webmin-auth.conf config/filter.d/wuftpd.conf @@ -177,15 +236,25 @@ config/filter.d/3proxy.conf config/filter.d/apache-common.conf config/filter.d/exim-common.conf config/filter.d/exim-spam.conf +config/filter.d/freeswitch.conf +config/filter.d/groupoffice.conf config/filter.d/perdition.conf config/filter.d/uwimap-auth.conf +config/filter.d/courier-auth.conf +config/filter.d/courier-smtp.conf +config/filter.d/ejabberd-auth.conf +config/filter.d/guacamole.conf +config/filter.d/sendmail-spam.conf config/action.d/apf.conf +config/action.d/blocklist_de.conf config/action.d/osx-afctl.conf config/action.d/osx-ipfw.conf config/action.d/sendmail-common.conf +config/action.d/badips.conf config/action.d/bsd-ipfw.conf config/action.d/dummy.conf -config/action.d/firewall-cmd-direct-new.conf +config/action.d/firewallcmd-new.conf +config/action.d/firewallcmd-ipset.conf config/action.d/iptables-ipset-proto6-allports.conf config/action.d/iptables-blocktype.conf config/action.d/iptables-ipset-proto4.conf @@ -210,9 +279,12 @@ config/action.d/mynetwatchman.conf config/action.d/pf.conf config/action.d/sendmail.conf config/action.d/sendmail-buffered.conf +config/action.d/sendmail-whois-ipmatches.conf config/action.d/sendmail-whois.conf config/action.d/sendmail-whois-lines.conf config/action.d/shorewall.conf +config/action.d/xarf-login-attack.conf +config/action.d/ufw.conf config/fail2ban.conf doc/run-rootless.txt man/fail2ban-client.1 diff --git a/README.md b/README.md index 24ed11ff..308136a1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ / _|__ _(_) |_ ) |__ __ _ _ _ | _/ _` | | |/ /| '_ \/ _` | ' \ |_| \__,_|_|_/___|_.__/\__,_|_||_| - v0.8.11 2013/11/13 + v0.9.0 2014/03/14 ## Fail2Ban: ban hosts that cause multiple authentication errors @@ -11,6 +11,11 @@ password failures. It updates firewall rules to reject the IP address. These rules can be defined by the user. Fail2Ban can read multiple log files such as sshd or Apache web server ones. +Fail2Ban is able to reduce the rate of incorrect authentications attempts +however it cannot eliminate the risk that weak authentication presents. +Configure services to use only two factor or public/private authentication +mechanisms if you really want to protect services. + This README is a quick introduction to Fail2ban. More documentation, FAQ, HOWTOs are available in fail2ban(1) manpage and on the website http://www.fail2ban.org @@ -21,21 +26,22 @@ Installation: this case, you should use it instead.** Required: -- [Python >= 2.4](http://www.python.org) +- [Python2 >= 2.6 or Python >= 3.2](http://www.python.org) or [PyPy](http://pypy.org) Optional: - [pyinotify >= 0.8.3](https://github.com/seb-m/pyinotify) - Linux >= 2.6.13 - [gamin >= 0.0.21](http://www.gnome.org/~veillard/gamin) +- [systemd >= 204](http://www.freedesktop.org/wiki/Software/systemd) To install, just do: - tar xvfj fail2ban-0.8.11.tar.bz2 - cd fail2ban-0.8.11 + tar xvfj fail2ban-0.9.0.tar.bz2 + cd fail2ban-0.9.0 python setup.py install -This will install Fail2Ban into /usr/share/fail2ban. The executable scripts are -placed into /usr/bin, and configuration under /etc/fail2ban. +This will install Fail2Ban into the python library directory. The executable +scripts are placed into /usr/bin, and configuration under /etc/fail2ban. Fail2Ban should be correctly installed now. Just type: @@ -50,8 +56,7 @@ Configuration: You can configure Fail2Ban using the files in /etc/fail2ban. It is possible to configure the server using commands sent to it by fail2ban-client. The available commands are described in the fail2ban-client(1) manpage. Also see -fail2ban(1) manpage for further references and find even more documentation on -the website: http://www.fail2ban.org +fail2ban(1) and jail.conf(5) manpages for further references. Code status: ------------ diff --git a/THANKS b/THANKS index 13303c21..64eb4402 100644 --- a/THANKS +++ b/THANKS @@ -6,71 +6,102 @@ the project. If you have been left off, please let us know (preferably send a pull request on github with the "fix") and you will be added +Adam Tkac Adrien Clerc ache +ag4ve (Shawn) +Alasdair D. Campbell Amir Caspi +Amy Andrey G. Grozin Andy Fragen Arturo 'Buanzo' Busleiman Axel Thimm +Bas van den Dikkenberg Beau Raines Bill Heaton Carlos Alberto Lopez Perez +cepheid666 Christian Rauch Christophe Carles Christoph Haas Christos Psonis Cyril Jaquier Daniel B. Cid +Daniel B. Daniel Black David Nutter +Derek Atkins Eric Gerbier Enrico Labedzki ftoppi François Boulogne Frédéric Georgiy Mernov +Guilhem Lettron Guillaume Delvit +Hank Leininger Hanno 'Rince' Wagner +Helmut Grohne Iain Lea +Ivo Truxa +John Thoe +Jacques Lav!gnotte +Ioan Indreias Jonathan Kamens Jonathan Lanning Jonathan Underwood Joël Bertrand JP Espinosa +jserrachinha Justin Shore Kévin Drapel kjohnsonecl kojiro +Lars Kneschke +Lee Clemens Manuel Arostegui Ramirez Marcel Dopita Mark Edgington Mark McKinstry +Mark White Markus Hoffmann Marvin Rouge mEDI Мернов Георгий +Merijn Schering Michael C. Haller Michael Hanselmann +Mika (mkl) Nick Munger +onorua +Noel Butler Patrick Börjesson Raphaël Marichez RealRancor René Berber Robert Edeker Rolf Fokkens +Roman Gelfand Russell Odom Sebastian Arcus Sireyessire silviogarbes +Stefan Tatschner Stephen Gildea Steven Hiscocks +TESTOVIK Tom Pike +Tomas Pihl +Tony Lawrence +Tomasz Ciolek Tyler Vaclav Misek Vincent Deffontaines Yaroslav Halchenko +Winston Smith ykimon Yehuda Katz +Zbigniew Jędrzejewski-Szmek zugeschmiert Zurd diff --git a/TODO b/TODO index 33263d3e..3c811a74 100644 --- a/TODO +++ b/TODO @@ -13,20 +13,6 @@ Legend: # partially done * done -- more detailed explaination in DEVELOP for new developers (eg. howto build this HEX numbers in ChangeLog) - -- Run tests though all filters/examples files - (see sshd example file) as unit - test - -* Removed relative imports - -* Cleanup fail2ban-client and fail2ban-server. Move code to server/ and client/ - -- Add timeout to external commands (signal alarm, watchdog thread, etc) - -- Uniformize filters and actions name. Use the software name (openssh, postfix, - proftp) and possible qualifier (e.g. auth) after a '-' - - Added tag for failregex. Add features using this information. Maybe add more tags @@ -37,23 +23,10 @@ Legend: - Auto-enable function (search for log files), check modification date to see if service is still in use -- Improve parsing of the action parameters in jailreader.py - - Better handling of the protocol in transmitter.py - Add gettext support (I18N) -- Multiline log reading - -- Improve execution of action. Why does subprocess.call deadlock with - multi-jails? - -# see Feature Request Tracking System at SourceForge.net - # improve documentation and website for user # better return values in function - -# refactoring in server.py, actions.py, filter.py - -* New backend: pyinotify diff --git a/fail2ban-client b/bin/fail2ban-client similarity index 95% rename from fail2ban-client rename to bin/fail2ban-client index 1237680b..8737c49d 100755 --- a/fail2ban-client +++ b/bin/fail2ban-client @@ -25,19 +25,11 @@ __license__ = "GPL" import sys, string, os, pickle, re, logging, signal import getopt, time, shlex, socket -# Inserts our own modules path first in the list -# fix for bug #343821 -try: - from common.version import version -except ImportError, e: - sys.path.insert(1, "/usr/share/fail2ban") - from common.version import version - -# Now we can import the rest of modules -from common.protocol import printFormatted -from client.csocket import CSocket -from client.configurator import Configurator -from client.beautifier import Beautifier +from fail2ban.version import version +from fail2ban.protocol import printFormatted +from fail2ban.client.csocket import CSocket +from fail2ban.client.configurator import Configurator +from fail2ban.client.beautifier import Beautifier # Gets the instance of the logger. logSys = logging.getLogger("fail2ban.client") @@ -110,7 +102,7 @@ class Fail2banClient: def __sigTERMhandler(self, signum, frame): # Print a new line because we probably come from wait print - logSys.warn("Caught signal %d. Exiting" % signum) + logSys.warning("Caught signal %d. Exiting" % signum) sys.exit(-1) def __getCmdLineOptions(self, optList): @@ -145,6 +137,7 @@ class Fail2banClient: def __processCmd(self, cmd, showRet = True): beautifier = Beautifier() + streamRet = True for c in cmd: beautifier.setInputCmd(c) try: @@ -158,7 +151,7 @@ class Fail2banClient: logSys.error("NOK: " + `ret[1].args`) if showRet: print beautifier.beautifyError(ret[1]) - return False + streamRet = False except socket.error: if showRet: logSys.error("Unable to contact server. Is it running?") @@ -167,7 +160,7 @@ class Fail2banClient: if showRet: logSys.error(e) return False - return True + return streamRet ## # Process a command line. @@ -332,7 +325,7 @@ class Fail2banClient: if verbose <= 0: logSys.setLevel(logging.ERROR) elif verbose == 1: - logSys.setLevel(logging.WARN) + logSys.setLevel(logging.WARNING) elif verbose == 2: logSys.setLevel(logging.INFO) else: diff --git a/fail2ban-regex b/bin/fail2ban-regex similarity index 54% rename from fail2ban-regex rename to bin/fail2ban-regex index 18e2a5df..270b70d7 100755 --- a/fail2ban-regex +++ b/bin/fail2ban-regex @@ -29,24 +29,23 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko" __copyright__ = "Copyright (c) 2004-2008 Cyril Jaquier, 2012-2013 Yaroslav Halchenko" __license__ = "GPL" -import getopt, sys, time, logging, os, urllib - -# Inserts our own modules path first in the list -# fix for bug #343821 -try: - from common.version import version -except ImportError, e: - sys.path.insert(1, "/usr/share/fail2ban") - from common.version import version - +import getopt, sys, time, logging, os, locale, shlex, urllib from optparse import OptionParser, Option -from client.configparserinc import SafeConfigParserWithIncludes from ConfigParser import NoOptionError, NoSectionError, MissingSectionHeaderError -from server.filter import Filter -from server.failregex import RegexException -from testcases.utils import FormatterWithTraceBack +try: + from systemd import journal + from fail2ban.server.filtersystemd import FilterSystemd +except ImportError: + journal = None + +from fail2ban.version import version +from fail2ban.client.filterreader import FilterReader +from fail2ban.server.filter import Filter +from fail2ban.server.failregex import RegexException + +from fail2ban.tests.utils import FormatterWithTraceBack # Gets the instance of the logger. logSys = logging.getLogger("fail2ban") @@ -72,6 +71,24 @@ def pprint_list(l, header=None): s = '' print s + "| " + "\n| ".join(l) + '\n`-' +def file_lines_gen(hdlr): + for line in hdlr: + try: + line = line.decode(fail2banRegex.encoding, 'strict') + except UnicodeDecodeError: + if sys.version_info >= (3,): # Python 3 must be decoded + line = line.decode(fail2banRegex.encoding, 'ignore') + yield line + +def journal_lines_gen(myjournal): + while True: + try: + entry = myjournal.get_next() + except OSError: + continue + if not entry: + break + yield FilterSystemd.formatJournalEntry(entry) def get_opt_parser(): # use module docstring for help output @@ -81,6 +98,7 @@ def get_opt_parser(): LOG: string a string representing a log line filename path to a log file (/var/log/auth.log) + "systemd-journal" search systemd journal (systemd-python required) REGEX: string a string representing a 'failregex' @@ -102,19 +120,32 @@ Report bugs to https://github.com/fail2ban/fail2ban/issues version="%prog " + version) p.add_options([ + Option("-d", "--datepattern", + help="set custom pattern used to match date/times"), + Option("-e", "--encoding", + help="File encoding. Default: system locale"), + Option("-L", "--maxlines", type=int, default=0, + help="maxlines for multi-line regex"), + Option("-m", "--journalmatch", + help="journalctl style matches overriding filter file. " + "\"systemd-journal\" only"), Option('-l', "--log-level", type="choice", dest="log_level", - choices=('heavydebug', 'debug', 'info', 'warning', 'error', 'fatal'), + choices=('heavydebug', 'debug', 'info', 'notice', 'warning', 'error', 'critical'), default=None, help="Log level for the Fail2Ban logger to use"), Option("-v", "--verbose", action='store_true', help="Be verbose in output"), Option("-D", "--debuggex", action='store_true', help="Produce debuggex.com urls for debugging there"), + Option("--print-no-missed", action='store_true', + help="Do not print any missed lines"), + Option("--print-no-ignored", action='store_true', + help="Do not print any ignored lines"), Option("--print-all-missed", action='store_true', - help="Either to print all missed lines"), + help="Print all missed lines, no matter how many"), Option("--print-all-ignored", action='store_true', - help="Either to print all ignored lines"), + help="Print all ignored lines, no matter how many"), Option("-t", "--log-traceback", action='store_true', help="Enrich log-messages with compressed tracebacks"), Option("--full-traceback", action='store_true', @@ -156,22 +187,16 @@ class LineStats(object): """ def __init__(self): self.tested = self.matched = 0 + self.missed = 0 self.missed_lines = [] self.missed_lines_timeextracted = [] + self.ignored = 0 self.ignored_lines = [] self.ignored_lines_timeextracted = [] def __str__(self): return "%(tested)d lines, %(ignored)d ignored, %(matched)d matched, %(missed)d missed" % self - @property - def ignored(self): - return len(self.ignored_lines) - - @property - def missed(self): - return self.tested - (self.ignored + self.matched) - # just for convenient str def __getitem__(self, key): return getattr(self, key) @@ -179,41 +204,88 @@ class LineStats(object): class Fail2banRegex(object): - CONFIG_DEFAULTS = {'configpath' : "/etc/fail2ban/"} - def __init__(self, opts): self._verbose = opts.verbose self._debuggex = opts.debuggex + self._maxlines = 20 + self._print_no_missed = opts.print_no_missed + self._print_no_ignored = opts.print_no_ignored self._print_all_missed = opts.print_all_missed self._print_all_ignored = opts.print_all_ignored + self._maxlines_set = False # so we allow to override maxlines in cmdline + self._datepattern_set = False + self._journalmatch = None self._filter = Filter(None) self._ignoreregex = list() self._failregex = list() self._line_stats = LineStats() + if opts.maxlines: + self.setMaxLines(opts.maxlines) + if opts.journalmatch is not None: + self.setJournalMatch(opts.journalmatch.split()) + if opts.datepattern: + self.setDatePattern(opts.datepattern) + if opts.encoding: + self.encoding = opts.encoding + else: + self.encoding = locale.getpreferredencoding() + + + + def setDatePattern(self, pattern): + if not self._datepattern_set: + self._filter.setDatePattern(pattern) + self._datepattern_set = True + if pattern is not None: + print "Use datepattern : %s" % ( + self._filter.getDatePattern()[1], ) + + def setMaxLines(self, v): + if not self._maxlines_set: + self._filter.setMaxLines(int(v)) + self._maxlines_set = True + print "Use maxlines : %d" % self._filter.getMaxLines() + + def setJournalMatch(self, v): + if self._journalmatch is None: + self._journalmatch = v def readRegex(self, value, regextype): assert(regextype in ('fail', 'ignore')) regex = regextype + 'regex' if os.path.isfile(value): - reader = SafeConfigParserWithIncludes(defaults=self.CONFIG_DEFAULTS) - try: - reader.read(value) - print "Use %11s file : %s" % (regex, value) - # TODO: reuse functionality in client + print "Use %11s file : %s" % (regex, value) + reader = FilterReader(value, 'fail2ban-regex-jail', {}) + reader.setBaseDir(None) + + if reader.readexplicit(): + reader.getOptions(None) + readercommands = reader.convert() 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 + RegexStat(m[3]) + for m in filter( + lambda x: x[0] == 'set' and x[2] == "add%sregex" % regextype, + readercommands)] + # Read out and set possible value of maxlines + 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: + print "ERROR: failed to read %s" % value return False else: print "Use %11s line : %s" % (regex, shortstr(value)) @@ -229,7 +301,7 @@ class Fail2banRegex(object): def testIgnoreRegex(self, line): found = False try: - ret = self._filter.ignoreLine(line) + ret = self._filter.ignoreLine([(line, "", "")]) if ret is not None: found = True regex = self._ignoreregex[ret].inc() @@ -238,9 +310,11 @@ class Fail2banRegex(object): return False return found - def testRegex(self, line): + def testRegex(self, line, date=None): + orgLineBuffer = self._filter._Filter__lineBuffer + fullBuffer = len(orgLineBuffer) >= self._filter.getMaxLines() try: - line, ret = self._filter.processLine(line, checkAllRegex=True) + line, ret = self._filter.processLine(line, date, checkAllRegex=True) for match in ret: # Append True/False flag depending if line was matched by # more than one regex @@ -254,41 +328,63 @@ class Fail2banRegex(object): except IndexError: print "Sorry, but no found in regex" return False + for bufLine in orgLineBuffer[int(fullBuffer):]: + if bufLine not in self._filter._Filter__lineBuffer: + try: + self._line_stats.missed_lines.pop( + self._line_stats.missed_lines.index("".join(bufLine))) + self._line_stats.missed_lines_timeextracted.pop( + self._line_stats.missed_lines_timeextracted.index( + "".join(bufLine[::2]))) + except ValueError: + pass + else: + self._line_stats.matched += 1 return line, ret - def process(self, test_lines): for line_no, line in enumerate(test_lines): - if line.startswith('#') or not line.strip(): - # skip comment and empty lines - continue - is_ignored = fail2banRegex.testIgnoreRegex(line) - line_datetimestripped, ret = fail2banRegex.testRegex(line) + if isinstance(line, tuple): + line_datetimestripped, ret = fail2banRegex.testRegex( + line[0], line[1]) + line = "".join(line[0]) + else: + line = line.rstrip('\r\n') + if line.startswith('#') or not line: + # skip comment and empty lines + continue + line_datetimestripped, ret = fail2banRegex.testRegex(line) + is_ignored = fail2banRegex.testIgnoreRegex(line_datetimestripped) if is_ignored: - self._line_stats.ignored_lines.append(line) - self._line_stats.ignored_lines_timeextracted.append(line_datetimestripped) + self._line_stats.ignored += 1 + if not self._print_no_ignored and (self._print_all_ignored or self._line_stats.ignored <= self._maxlines + 1): + self._line_stats.ignored_lines.append(line) + self._line_stats.ignored_lines_timeextracted.append(line_datetimestripped) if len(ret) > 0: assert(not is_ignored) self._line_stats.matched += 1 else: if not is_ignored: - self._line_stats.missed_lines.append(line) - self._line_stats.missed_lines_timeextracted.append(line_datetimestripped) + self._line_stats.missed += 1 + if not self._print_no_missed and (self._print_all_missed or self._line_stats.missed <= self._maxlines + 1): + self._line_stats.missed_lines.append(line) + self._line_stats.missed_lines_timeextracted.append(line_datetimestripped) self._line_stats.tested += 1 - if line_no % 10 == 0: + if line_no % 10 == 0 and self._filter.dateDetector is not None: self._filter.dateDetector.sortTemplate() def printLines(self, ltype): lstats = self._line_stats - assert(len(lstats.missed_lines) == lstats.tested - (lstats.matched + lstats.ignored)) + assert(self._line_stats.missed == lstats.tested - (lstats.matched + lstats.ignored)) + lines = lstats[ltype] l = lstats[ltype + '_lines'] - if len(l): + if lines: header = "%s line(s):" % (ltype.capitalize(),) if self._debuggex: if ltype == 'missed': @@ -296,21 +392,20 @@ class Fail2banRegex(object): else: regexlist = self._ignoreregex l = lstats[ltype + '_lines_timeextracted'] - lines = len(l)*len(regexlist) - if lines < 20 or getattr(self, '_print_all_' + ltype): + if lines < self._maxlines or getattr(self, '_print_all_' + ltype): ans = [[]] for arg in [l, regexlist]: ans = [ x + [y] for x in ans for y in arg ] b = map(lambda a: a[0] + ' | ' + a[1].getFailRegex() + ' | ' + debuggexURL(a[0], a[1].getFailRegex()), ans) pprint_list([x.rstrip() for x in b], header) else: - print "%s: too many to print. Use --print-all-%s " \ + print "%s too many to print. Use --print-all-%s " \ "to print all %d lines" % (header, ltype, lines) - elif len(l) < 20 or getattr(self, '_print_all_' + ltype): + elif lines < self._maxlines or getattr(self, '_print_all_' + ltype): pprint_list([x.rstrip() for x in l], header) else: - print "%s: too many to print. Use --print-all-%s " \ - "to print all %d lines" % (header, ltype, len(l)) + print "%s too many to print. Use --print-all-%s " \ + "to print all %d lines" % (header, ltype, lines) def printStats(self): print @@ -334,7 +429,7 @@ class Fail2banRegex(object): " %s %s%s" % ( ip[1], timeString, - ip[3] and " (multiple regex matched)" or "")) + ip[-1] and " (multiple regex matched)" or "")) print "\n%s: %d total" % (title, total) pprint_list(out, " #) [# of hits] regular expression") @@ -345,17 +440,21 @@ class Fail2banRegex(object): _ = print_failregexes("Ignoreregex", self._ignoreregex) - print "\nDate template hits:" - out = [] - for template in self._filter.dateDetector.getTemplates(): - if self._verbose or template.getHits(): - out.append("[%d] %s" % (template.getHits(), template.getName())) - pprint_list(out, "[# of hits] date format") + if self._filter.dateDetector is not None: + print "\nDate template hits:" + out = [] + for template in self._filter.dateDetector.templates: + if self._verbose or template.hits: + out.append("[%d] %s" % ( + template.hits, template.name)) + pprint_list(out, "[# of hits] date format") print "\nLines: %s" % self._line_stats - self.printLines('ignored') - self.printLines('missed') + if not self._print_no_ignored: + self.printLines('ignored') + if not self._print_no_missed: + self.printLines('missed') return True @@ -364,6 +463,19 @@ if __name__ == "__main__": parser = get_opt_parser() (opts, args) = parser.parse_args() + if opts.print_no_missed and opts.print_all_missed: + sys.stderr.write("ERROR: --print-no-missed and --print-all-missed are mutually exclusive.\n\n") + parser.print_help() + sys.exit(-1) + if opts.print_no_ignored and opts.print_all_ignored: + sys.stderr.write("ERROR: --print-no-ignored and --print-all-ignored are mutually exclusive.\n\n") + parser.print_help() + sys.exit(-1) + + print + print "Running tests" + print "=============" + print fail2banRegex = Fail2banRegex(opts) @@ -379,9 +491,9 @@ if __name__ == "__main__": logSys.setLevel(getattr(logging, opts.log_level.upper())) else: # pragma: no cover # suppress the logging but it would leave unittests' progress dots - # ticking, unless like with '-l fatal' which would be silent + # ticking, unless like with '-l critical' which would be silent # unless error occurs - logSys.setLevel(getattr(logging, 'FATAL')) + logSys.setLevel(getattr(logging, 'CRITICAL')) # Add the default logging handler stdout = logging.StreamHandler(sys.stdout) @@ -395,33 +507,48 @@ if __name__ == "__main__": Formatter = logging.Formatter # Custom log format for the verbose tests runs - if opts.verbose > 1: # pragma: no cover + if opts.verbose: # pragma: no cover stdout.setFormatter(Formatter(' %(asctime)-15s %(thread)s' + fmt)) else: # pragma: no cover # just prefix with the space stdout.setFormatter(Formatter(fmt)) logSys.addHandler(stdout) - print - print "Running tests" - print "=============" - print - cmd_log, cmd_regex = args[:2] + fail2banRegex.readRegex(cmd_regex, 'fail') or sys.exit(-1) + if len(args) == 3: fail2banRegex.readRegex(args[2], 'ignore') or sys.exit(-1) - fail2banRegex.readRegex(cmd_regex, 'fail') or sys.exit(-1) - if os.path.isfile(cmd_log): try: - hdlr = open(cmd_log) + hdlr = open(cmd_log, 'rb') print "Use log file : %s" % cmd_log - test_lines = hdlr # Iterable + print "Use encoding : %s" % fail2banRegex.encoding + test_lines = file_lines_gen(hdlr) except IOError, e: print e sys.exit(-1) + elif cmd_log == "systemd-journal": + if not journal: + print "Error: systemd library not found. Exiting..." + sys.exit(-1) + myjournal = journal.Reader(converters={'__CURSOR': lambda x: x}) + journalmatch = fail2banRegex._journalmatch + fail2banRegex.setDatePattern(None) + if journalmatch: + try: + for element in journalmatch: + if element == "+": + myjournal.add_disjunction() + else: + myjournal.add_match(element) + except ValueError: + print "Error: Invalid journalmatch: %s" % shortstr(" ".join(journalmatch)) + sys.exit(-1) + print "Use journal match : %s" % " ".join(journalmatch) + test_lines = journal_lines_gen(myjournal) else: print "Use single line : %s" % shortstr(cmd_log) test_lines = [ cmd_log ] diff --git a/fail2ban-server b/bin/fail2ban-server similarity index 93% rename from fail2ban-server rename to bin/fail2ban-server index 404a1ced..aba19ab5 100755 --- a/fail2ban-server +++ b/bin/fail2ban-server @@ -24,15 +24,8 @@ __license__ = "GPL" import getopt, sys, logging, os -# Inserts our own modules path first in the list -# fix for bug #343821 -try: - from common.version import version -except ImportError, e: - sys.path.insert(1, "/usr/share/fail2ban") - from common.version import version - -from server.server import Server +from fail2ban.version import version +from fail2ban.server.server import Server # Gets the instance of the logger. logSys = logging.getLogger("fail2ban") @@ -104,10 +97,10 @@ class Fail2banServer: if opt[0] == "-x": self.__conf["force"] = True if opt[0] in ["-h", "--help"]: - self.dispUsage() + self.dispUsage() sys.exit(0) if opt[0] in ["-V", "--version"]: - self.dispVersion() + self.dispVersion() sys.exit(0) def start(self, argv): diff --git a/bin/fail2ban-testcases b/bin/fail2ban-testcases new file mode 100755 index 00000000..b3bddf1c --- /dev/null +++ b/bin/fail2ban-testcases @@ -0,0 +1,128 @@ +#!/usr/bin/python +# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*- +# vi: set ft=python sts=4 ts=4 sw=4 noet : +"""Script to run Fail2Ban tests battery +""" + +# This file is part of Fail2Ban. +# +# Fail2Ban is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Fail2Ban is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Fail2Ban; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +__author__ = "Cyril Jaquier" +__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2012- Yaroslav Halchenko" +__license__ = "GPL" + + +import unittest, logging, sys, time, os + +# Check if local fail2ban module exists, and use if it exists by +# modifying the path. This is such that tests can be used in dev +# environment. +if os.path.exists("fail2ban/__init__.py"): + sys.path.insert(0, ".") +from fail2ban.version import version + +from fail2ban.tests.utils import FormatterWithTraceBack, gatherTests +from fail2ban.server.mytime import MyTime + +from optparse import OptionParser, Option + +def get_opt_parser(): + # use module docstring for help output + p = OptionParser( + usage="%s [OPTIONS] [regexps]\n" % sys.argv[0] + __doc__, + version="%prog " + version) + + p.add_options([ + Option('-l', "--log-level", type="choice", + dest="log_level", + choices=('heavydebug', 'debug', 'info', 'notice', 'warning', 'error', 'critical'), + default=None, + help="Log level for the logger to use during running tests"), + Option('-n', "--no-network", action="store_true", + dest="no_network", + help="Do not run tests that require the network"), + Option("-t", "--log-traceback", action='store_true', + help="Enrich log-messages with compressed tracebacks"), + Option("--full-traceback", action='store_true', + help="Either to make the tracebacks full, not compressed (as by default)"), + + ]) + + return p + +parser = get_opt_parser() +(opts, regexps) = parser.parse_args() + +# +# Logging +# +logSys = logging.getLogger("fail2ban") + +# Numerical level of verbosity corresponding to a log "level" +verbosity = {'heavydebug': 4, + 'debug': 3, + 'info': 2, + 'notice': 2, + 'warning': 1, + 'error': 1, + 'critical': 0, + None: 1}[opts.log_level] + +if opts.log_level is not None: # pragma: no cover + # so we had explicit settings + logSys.setLevel(getattr(logging, opts.log_level.upper())) +else: # pragma: no cover + # suppress the logging but it would leave unittests' progress dots + # ticking, unless like with '-l critical' which would be silent + # unless error occurs + logSys.setLevel(getattr(logging, 'CRITICAL')) + +# Add the default logging handler +stdout = logging.StreamHandler(sys.stdout) + +fmt = ' %(message)s' + +if opts.log_traceback: + Formatter = FormatterWithTraceBack + fmt = (opts.full_traceback and ' %(tb)s' or ' %(tbc)s') + fmt +else: + Formatter = logging.Formatter + +# Custom log format for the verbose tests runs +if verbosity > 1: # pragma: no cover + stdout.setFormatter(Formatter(' %(asctime)-15s %(thread)s' + fmt)) +else: # pragma: no cover + # just prefix with the space + stdout.setFormatter(Formatter(fmt)) +logSys.addHandler(stdout) + +# +# Let know the version +# +if not opts.log_level or opts.log_level != 'critical': # pragma: no cover + print("Fail2ban %s test suite. Python %s. Please wait..." \ + % (version, str(sys.version).replace('\n', ''))) + +tests = gatherTests(regexps, opts.no_network) +# +# Run the tests +# +testRunner = unittest.TextTestRunner(verbosity=verbosity) + +tests_results = testRunner.run(tests) + +if not tests_results.wasSuccessful(): # pragma: no cover + sys.exit(1) diff --git a/client/actionreader.py b/client/actionreader.py deleted file mode 100644 index 8f60b55b..00000000 --- a/client/actionreader.py +++ /dev/null @@ -1,90 +0,0 @@ -# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*- -# vi: set ft=python sts=4 ts=4 sw=4 noet : - -# This file is part of Fail2Ban. -# -# Fail2Ban is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Fail2Ban is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Fail2Ban; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -# Author: Cyril Jaquier -# - -__author__ = "Cyril Jaquier" -__copyright__ = "Copyright (c) 2004 Cyril Jaquier" -__license__ = "GPL" - -import logging -from configreader import ConfigReader - -# Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.client.config") - -class ActionReader(ConfigReader): - - def __init__(self, action, name, **kwargs): - ConfigReader.__init__(self, **kwargs) - self.__file = action[0] - self.__cInfo = action[1] - self.__name = name - - def setFile(self, fileName): - self.__file = fileName - - def getFile(self): - return self.__file - - def setName(self, name): - self.__name = name - - def getName(self): - return self.__name - - def read(self): - return ConfigReader.read(self, "action.d/" + self.__file) - - def getOptions(self, pOpts): - opts = [["string", "actionstart", ""], - ["string", "actionstop", ""], - ["string", "actioncheck", ""], - ["string", "actionban", ""], - ["string", "actionunban", ""]] - self.__opts = ConfigReader.getOptions(self, "Definition", opts, pOpts) - - if self.has_section("Init"): - for opt in self.options("Init"): - if not self.__cInfo.has_key(opt): - self.__cInfo[opt] = self.get("Init", opt) - - def convert(self): - head = ["set", self.__name] - stream = list() - stream.append(head + ["addaction", self.__file]) - for opt in self.__opts: - if opt == "actionstart": - stream.append(head + ["actionstart", self.__file, self.__opts[opt]]) - elif opt == "actionstop": - stream.append(head + ["actionstop", self.__file, self.__opts[opt]]) - elif opt == "actioncheck": - stream.append(head + ["actioncheck", self.__file, self.__opts[opt]]) - elif opt == "actionban": - stream.append(head + ["actionban", self.__file, self.__opts[opt]]) - elif opt == "actionunban": - stream.append(head + ["actionunban", self.__file, self.__opts[opt]]) - # cInfo - if self.__cInfo: - for p in self.__cInfo: - stream.append(head + ["setcinfo", self.__file, p, self.__cInfo[p]]) - - return stream - diff --git a/client/filterreader.py b/client/filterreader.py deleted file mode 100644 index f75190f9..00000000 --- a/client/filterreader.py +++ /dev/null @@ -1,74 +0,0 @@ -# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*- -# vi: set ft=python sts=4 ts=4 sw=4 noet : - -# This file is part of Fail2Ban. -# -# Fail2Ban is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Fail2Ban is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Fail2Ban; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -# Author: Cyril Jaquier -# - -__author__ = "Cyril Jaquier" -__copyright__ = "Copyright (c) 2004 Cyril Jaquier" -__license__ = "GPL" - -import logging -from configreader import ConfigReader - -# Gets the instance of the logger. -logSys = logging.getLogger("fail2ban.client.config") - -class FilterReader(ConfigReader): - - def __init__(self, fileName, name, **kwargs): - ConfigReader.__init__(self, **kwargs) - self.__file = fileName - self.__name = name - - def setFile(self, fileName): - self.__file = fileName - - def getFile(self): - return self.__file - - def setName(self, name): - self.__name = name - - def getName(self): - return self.__name - - def read(self): - return ConfigReader.read(self, "filter.d/" + self.__file) - - def getOptions(self, pOpts): - opts = [["string", "ignoreregex", ""], - ["string", "failregex", ""]] - self.__opts = ConfigReader.getOptions(self, "Definition", opts, pOpts) - - def convert(self): - stream = list() - for opt in self.__opts: - if opt == "failregex": - for regex in self.__opts[opt].split('\n'): - # Do not send a command if the rule is empty. - if regex != '': - stream.append(["set", self.__name, "addfailregex", regex]) - elif opt == "ignoreregex": - for regex in self.__opts[opt].split('\n'): - # Do not send a command if the rule is empty. - if regex != '': - stream.append(["set", self.__name, "addignoreregex", regex]) - return stream - diff --git a/config/action.d/apf.conf b/config/action.d/apf.conf index 9af3066d..5c4a2614 100644 --- a/config/action.d/apf.conf +++ b/config/action.d/apf.conf @@ -1,43 +1,25 @@ # Fail2Ban configuration file +# https://www.rfxn.com/projects/advanced-policy-firewall/ # -# Author: Mark McKinstry +# Note: APF doesn't play nicely with other actions. It has been observed to +# remove bans created by other iptables based actions. If you are going to use +# this action, use it for all of your jails. # +# DON'T MIX APF and other IPTABLES based actions [Definition] -# Option: actionstart -# Notes.: command executed once at the start of Fail2Ban. -# Values: CMD -# actionstart = - -# Option: actionstop -# Notes.: command executed once at the end of Fail2Ban -# Values: CMD -# actionstop = - -# Option: actioncheck -# Notes.: command executed once before each actionban command -# Values: CMD -# actioncheck = - -# Option: actionban -# Notes.: command executed when banning an IP. Take care that the -# command is executed with Fail2Ban user rights. -# Tags: IP address -# number of failures -#