Compare commits

..
246 Commits
Author SHA1 Message Date
sebres 095aeda840 Merge branch '0.11' 2021-12-02 18:56:19 +01:00
sebres 3fb02527a4 Merge branch '0.10' into 0.11 2021-12-02 18:52:13 +01:00
sebres 25a61ce632 Merge branch 'gh-actions--test-systemd' into 0.10 + several revisions cherry-picked from 0.11/master 2021-12-02 18:48:59 +01:00
sebres 21d94ff178 amend to fix gh-3098: no option --disable-2to3 anymore 2021-12-02 18:44:24 +01:00
sebres 196c55e931 fix gh-3098: build fails with error in fail2ban setup command: use_2to3 is invalid (setuptools 58+) 2021-12-02 18:44:22 +01:00
sebres 0fa76ef75a gh-actions: temporary ignore tests of systemd backend for python >= v.3.10 (otherwise it fails with "PY_SSIZE_T_CLEAN macro must be defined for '#' formats") 2021-12-02 18:33:10 +01:00
sebres a147a8b0e1 gh-actions: coverage for systemd backend (to monitor journals in test-suite in GHA-env we need to use 0 as default flags, because otherwise it cannot be found using SYSTEM_ONLY(4)) 2021-12-02 18:33:08 +01:00
sebres 1bcb62e31c gh-actions: python releases upgrade + debug/test systemd backend availability in GHA 2021-12-02 18:33:05 +01:00
sebres a57643404c mytime.seconds2str: small amend with speed-up, code simplification and few tests 2021-11-04 14:34:04 +01:00
sebres 80805cabfc Merge branch '0.11' 2021-11-03 16:01:00 +01:00
sebres 4fe4ac8dde amend to merge: replace timedelta string representation with new function seconds2str 2021-11-03 15:58:57 +01:00
sebres 0b3ad780fe Merge branch '0.10' into 0.11 2021-11-03 15:48:21 +01:00
sebres ebf5784b8c Merge branch 'fix-gh-2882' into 0.10
closes gh-2882
2021-11-03 15:47:45 +01:00
sebres 3b02098817 several backends optimizations (in file and journal filters):
- don't need to wait if we still had log-entries from last iteration (which got interrupted for servicing)
- rewritten update log/journal position, it is more stable and faster now (fewer DB access and surely up-to-date at end)
2021-11-03 15:41:50 +01:00
sebres 96661f25ab filtersystemd.py: fixes wrong time point of "in operation" mode
todo: need more tests to cover any step of switch to inOperationMode (all branches)
2021-11-03 15:41:40 +01:00
sebres 7678f59827 better format of time delta (using seconds2str); increase stability for systemd test-cases 2021-11-03 12:57:57 +01:00
sebres 4b54a07d71 Revert "action.d/firewallcmd-*.conf (multiport only): fixed port range selector, replacing : with -;"
This reverts the incompatibility #3047 introduced by commit a038fd5dfe (#2821).
2021-11-01 11:45:40 +01:00
Sergey G. Brester e74baae666 Merge pull request #3135 from sylvestre/patch-2
Add the Debian path to roundcube error logs
2021-10-25 12:11:42 +02:00
Sergey G. Brester cb042dda7d Merge pull request #3133 from sylvestre/master
Fix typo found by lintian (Debian static analyzer)
2021-10-25 12:10:28 +02:00
Sylvestre Ledru 3245b8018b Add the Debian path to roundcube error logs 2021-10-23 17:38:20 +02:00
Sylvestre Ledru 1cd3118da3 Fix typo found by lintian (Debian static analyzer) 2021-10-23 17:06:13 +02:00
Sergey G. Brester 98c7dd04a4 Merge pull request #3037 from floppym/bug794931
tests: improve detection of readable systemd journal
2021-10-22 15:34:47 +02:00
Mike Gilbert d91d949e95 tests: improve detection of readable systemd journal
Look for system.journal in journal sub-directory.
Add -readable to the find command.

Bug: https://bugs.gentoo.org/794931
2021-10-19 11:08:04 -04:00
Viktor Szépe 1e8ce20b32 Display ChangeLog as a Markdown document 2021-10-17 19:53:10 +02:00
Sergey G. Brester 8e3a26bdeb Merge pull request #3117 from fail2ban/gh-3116
filter.d/lighttpd-auth.conf: adjust to the current source code, avoid catch-all's, etc
2021-10-01 15:09:09 +02:00
Sergey G. Brester ba839af8ad filter.d/lighttpd-auth.conf: adjusted to the current source code + avoiding catch-all's, etc (gh-3116) 2021-10-01 15:03:24 +02:00
Sergey G. Brester f8f59dd31a added test cases covering different messages adjusted to new log-format (gh-3116) 2021-10-01 14:58:25 +02:00
Sergey G. Brester 5ee482bc9a Merge pull request #3053 from db48x/fix-grammar-of-timestamp-warnings
Improve grammar and readability of timestamp warnings
2021-09-21 16:16:52 +02:00
Sergey G. Brester d086317cc8 Update filter.py 2021-09-21 16:05:53 +02:00
Sergey G. Brester 17eed32e03 Update filtertestcase.py 2021-09-21 16:00:37 +02:00
sebres 621d8cae17 restore backwards compatibility for date None 2021-09-20 02:20:22 +02:00
sebres ec043cd202 simplifying logic and shortening messages (delta in minutes; removed clock synchronization, because it is rarely an issue on fail2ban side, e. g. for remote logs only, etc) 2021-09-19 21:58:42 +02:00
Daniel Brooks d7afcde2e1 add a warning message for dates in the future
and a test that checks which message was output for which time deltas.
2021-09-19 19:39:52 +02:00
Daniel Brooks 1929e7a76b include more specific information in the warning 2021-09-19 19:39:49 +02:00
Daniel Brooks 320a3dcdd5 remove old warnings from filtertestcase.py
assertLogged only checks that at least one listed message is found, so
it isn’t necessary to repeat them in the test.
2021-09-19 19:39:45 +02:00
Daniel Brooks a98cc08b31 Updated the warning messages created when fail2ban sees unexpected timestamps
to improve their grammar and to remove jargon.

Partially fixes #2822
2021-09-19 19:39:41 +02:00
sebres 5093ff8956 Merge branch '0.11' into master 2021-09-19 18:54:23 +02:00
sebres d6b884f3b7 amend to fix gh-3098: no option --disable-2to3 anymore 2021-09-19 18:52:34 +02:00
sebres 5ac303df8a fix gh-3098: build fails with error in fail2ban setup command: use_2to3 is invalid (setuptools 58+) 2021-09-19 18:49:18 +02:00
sebres 8d45deca86 Merge branch '0.10' into 0.11 2021-09-19 18:42:23 +02:00
sebres 974ba688d4 Merge branch 'patch-3098' into 0.10 2021-09-19 18:41:24 +02:00
Sergey G. Brester 7f22c4873a remove 2to3 in setup (should be called outside before setup) 2021-09-19 18:36:02 +02:00
Sergey G. Brester 1414a44b8e Update main.yml
CI: try to install dependencies via apt, add build test
2021-09-19 18:24:36 +02:00
sebres ad1f9dc4d0 Merge branch '0.11' 2021-09-08 20:12:48 +02:00
sebres 64217fe018 Merge branch '0.10' into 0.11 2021-09-08 20:09:48 +02:00
sebres c0f9348db5 Merge branch 'sebres/gh-3097--fix-unh-except' into 0.10;
closes #3097
2021-09-08 20:08:30 +02:00
sebres d709ec8179 GH actions: use newest python version for 3.10 (3.10.0-rc.2) 2021-09-08 20:00:41 +02:00
sebres ba282b794c pyinotify: amend to 1e4a14fb25: one fix more for sporadic runtime error "dictionary changed size during iteration" (watched files) 2021-09-08 19:56:02 +02:00
sebres e323c148e1 backend systemd: fixes error "local variable 'line' referenced before assignment", introduced in 55d7d9e214f72bbe4f39a2d17aa004d80bfc7299;
don't update database too often (every 10 ticks or ~ 10 seconds in production);
closes gh-3097
2021-09-08 19:44:49 +02:00
sebres 1e4a14fb25 pyinotify: fixes sporadic runtime error "dictionary changed size during iteration" (if something outside changes the pending dict during _checkPending evaluation) - simply deserialize to a list for iteration, without any lock, because unneeded here due to small and mostly empty dictionary (logrotate, etc), not to mention that pending check is normally called once per minute;
don't call process file inside of server thread calling of addLogPath (always retard it as pending event);
ensure to wake-up as soon as possible to process pending events (e. g. if file gets added).
2021-09-08 19:17:44 +02:00
sebres 2f99d5accb test coverage for unhandled exception in run of several filter (gh-3097) 2021-09-08 18:22:31 +02:00
Sergey G. Brester cb667edf17 Merge pull request #3087 from alexporto2200/patch-3
README: added dependencies to setuptools or distutils (if installing from source)
2021-08-17 16:49:40 +02:00
Sergey G. Brester ade79635b2 distutils/setuptools only required if installing from source 2021-08-17 16:41:20 +02:00
Alex Porto dos Santos 39fe0bdce6 Update Readme
Add python3-setuptools or python-setuptools for dependencies. On some distributions this doesn't come by default, it would be nice to let users know about this in the documentation.
2021-08-17 10:39:04 -03:00
sebres 10cd815525 merge 0.11 to 1.0 (GHSA-m985-3f3v-cwmm) 2021-07-07 12:06:06 +02:00
sebres c03fe6682c merge 0.10 to 0.11 (GHSA-m985-3f3v-cwmm) 2021-07-07 12:04:46 +02:00
sebres e3f2fcfab4 merge point (GHSA-m985-3f3v-cwmm 0.9/0.10) 2021-07-07 11:50:49 +02:00
sebres 2ed414ed09 fixed possible RCE vulnerability, unset escape variable (default tilde) stops consider "~" char after new-line as composing escape sequence
closes GHSA-m985-3f3v-cwmm for 0.9
2021-07-07 11:46:28 +02:00
sebres 410a6ce5c8 fixed possible RCE vulnerability, unset escape variable (default tilde) stops consider "~" char after new-line as composing escape sequence 2021-06-21 17:12:53 +02:00
sebres 579c6a94af filter.d/postfix.conf: mode ddos (and aggressive) extended to consider abusive handling of clients hitting command limit (gh-3040) 2021-06-10 15:23:24 +02:00
sebres 43f2923fbd filter.d/postfix.conf: matches rejects with "undeliverable address" (sender/recipient verification, gh-3039) additionally to "Unknown user";
both are configurable now via extended parameter and can be disabled using `exre-user=` supplied in filter parameters
2021-06-10 15:06:54 +02:00
Sergey G. Brester bbfff18280 action.d/ufw.conf: amend to #3018: parameter kill-mode extended with conntrack 2021-06-03 12:02:08 +02:00
sebres c7a86b4616 action.d/firewallcmd-ipset.conf: amend to #2620:
- combines actions `firewallcmd-ipset` and `firewallcmd-ipset-native` (parameter `ipsettype=firewalld`);
- IPv6-capability for firewalld ipset;
- no internal timeout handling by default;
- no permanent rules yet
2021-05-29 22:59:55 +02:00
Sergey G. Brester 2a508da5a0 Merge pull request #2620 from mspolitaev/master
Using native firewalld ipset implementation
2021-05-29 21:30:55 +02:00
sebres 38535b0cca Merge branch '0.11' into master 2021-05-29 21:25:24 +02:00
sebres d2f5c7de09 Merge branch '0.10' into 0.11 2021-05-29 21:24:11 +02:00
sebres 92f90038fa filter.d/dovecot.conf: extended to match prefix like conn unix:auth-worker (uid=143): auth-worker<13247>: (authenticate from external service like exim), gh-2553 2021-05-29 21:12:34 +02:00
sebres 8b984a0135 filter.d\exim-common.conf: pid-prefix extended to match mx1 exim[...]: (gh-2553) 2021-05-29 20:47:56 +02:00
sebres 6be1a5a0b1 filter.d/dovecot.conf: fixed "Authentication failure" regex, matches "Password mismatch" in title case (gh-2880) 2021-05-29 20:25:28 +02:00
sebres 8afea37494 filter.d/sendmail-auth.conf: covering several "authentication failure" messages, sendmail 8.16.1 (gh-2757) 2021-05-29 20:09:57 +02:00
sebres c5f1598a21 filter.d/postfix.conf: extended to cover new vectors:
- reject: BDAT/DATA from (gh-2927)
- (since regex is more precise now) token selector changed to `[A-Z]{4}`, e. g. no matter what a command is supplied now (RCPT, EHLO, VRFY, DATA, BDAT or something else)
- matches "Command rejected" and "Data command rejected" now
2021-05-29 19:48:24 +02:00
sebres ae3e9b9149 filter.d/postfix.conf: extended to cover 2 new vectors:
- RCPT from unknown, 504 5.5.2, need fully-qualified hostname, gh-2995
- 550 5.7.25 Client host rejected, gh-2996
review combining several regex to single one
2021-05-29 19:21:27 +02:00
sebres 87f717e0e0 filter.d/sendmail-reject.conf: fix reverse DNS for ... (gh-3012) 2021-05-29 18:45:59 +02:00
Sergey G. Brester 3d52fe3e4e Merge pull request #2679 from mikaku/updated-to-latest-jail.conf
Add new jail (and filter) Monitorix
2021-05-27 12:17:16 +02:00
sebres 0a05dbdbfc Merge branch '0.11' into master 2021-05-25 23:19:25 +02:00
sebres 3312b8cb95 Merge branch '0.10' into 0.11 2021-05-25 23:18:33 +02:00
sebres 1627d4f573 filter.d/sendmail-auth.conf: user not found, closes gh-3030 2021-05-25 23:16:29 +02:00
Sergey G. Brester f07e0f7ade Merge pull request #2984 from j-marz/zoneminder_filter_update
Zoneminder filter update
2021-05-21 13:03:33 +02:00
Sergey G. Brester ec4e0dd65b padding with space, prefregex, regex review (simplifying, capture user name, consider possible space char in user name) 2021-05-21 13:00:24 +02:00
j-marz 2367ad115c fixed typo in comment 2021-05-20 09:15:45 +10:00
Ioannis Cherouvim 5e3de882af docs: Typo. (#3025) 2021-05-12 13:18:46 +02:00
Sergey G. Brester 3f9cf27853 filter.d/apache-fakegooglebot.conf: better, more precise regex and datepattern (closes possible weakness like #3013) 2021-05-11 13:47:48 +02:00
usernamepi 4f8427178a Missing comment "#" (#3022)
Missed this ... but the logs showed it.
2021-05-07 18:23:40 +02:00
sebres 6ac2156907 Merge branch '0.11' into master 2021-05-07 01:48:36 +02:00
sebres 3e1aa03037 Merge branch '0.10' into 0.11 2021-05-07 01:46:46 +02:00
sebres ef5c826c74 fixes search for the best datepattern (gh-3020) - e. g. if line is too short, boundaries check for previously known unprecise pattern may fail on incomplete lines (logging break-off, no flush, etc) 2021-05-07 01:18:54 +02:00
sebres 2918849f9e fixes precise year pattern %ExY - accept years 20xx up to current century (using almost the same pattern in tests and production now) 2021-05-07 01:10:26 +02:00
usernamepi 88f779ed24 ufw.conf, amend to #3018 - add missing option for comment (#3019) 2021-05-06 23:23:39 +02:00
Sergey G. Brester 5a8f1bceb8 Merge pull request #3018 from usernamepi/master
Update ufw.conf: several fixes and enhancements
2021-05-06 22:20:39 +02:00
Sergey G. Brester 2958ad8636 Update ChangeLog 2021-05-06 22:19:38 +02:00
Sergey G. Brester 8f6a8df3a4 added new options kill-mode and kill, which makes the drop of all connections optional 2021-05-06 21:47:06 +02:00
Sergey G. Brester 5debaa4cac option "add", can be set to "insert <num>" instead of prepend (customization or backwards compat) 2021-05-06 20:23:58 +02:00
usernamepi e4e7a83cff Update ufw.conf
Prerequisites:
* The ss command is available, kernel is compiled with option CONFIG_INET_DIAG_DESTROY.
* Ufw version is => 0.36 (released in 2018)

* Now using "prepend" instead of "insert" to be able to handle IPv6 addresses correctly. The current action will fail for IPv6 addresses.
* Now application names containing a space should handled correctly, solves https://github.com/fail2ban/fail2ban/pull/1532
* Now closing IPv4 and IPv6 connections (if any) from the ip that is being banned. The current action will leave them open.
   Using ss to accomplish this. For this to work the kernel needs to be compiled with the CONFIG_INET_DIAG_DESTROY option.
   My system apparently is compiled that way.
2021-05-06 13:44:36 +02:00
sebres 71ce548117 Merge branch '0.11' 2021-04-27 14:05:53 +02:00
sebres b5b615731e Merge branch '0.10' into 0.11 2021-04-27 14:03:49 +02:00
sebres 319cfefac2 fix travis build (unsupported pythons and pypy versions), update 3.10 in GH actions 2021-04-27 13:41:57 +02:00
sebres d3f5d2d52b documentation (interpolation tags) 2021-04-21 11:50:07 +02:00
sebres f0214b3d36 filter.d/sendmail-reject.conf: fixed regex to consider "Connection rate limit exceeded" with different combination of arguments 2021-04-20 18:13:40 +02:00
Sergey G. Brester ab0847e2d5 more precise anchored RE (also combining all 3 REs in a single regex) 2021-04-14 13:06:58 +02:00
Jordi Sanfeliu b6fac90b5a Update monitorix 2021-04-14 11:46:23 +02:00
Jordi Sanfeliu 63b3f39adc Rename monitorix-httpd to monitorix 2021-04-14 11:30:48 +02:00
Jordi Sanfeliu 8b741129a5 Create monitorix-httpd 2021-04-14 11:26:52 +02:00
Jordi Sanfeliu 7d173b7ce0 Merge branch 'master' into updated-to-latest-jail.conf 2021-04-13 20:24:08 +02:00
sebres 6893d5a8b7 Merge remote-tracking branch 'remotes/gh-upstream/0.11' into master 2021-04-11 19:05:02 +02:00
Sergey G. Brester d74dd9321b Merge pull request #2565 from caronc/0.11
Add Apprise Support (50+ Notifications)
2021-04-04 00:24:21 +02:00
Sergey G. Brester b2f6a3a658 remove unneeded substitution
it is enough to add `apprise` to action
2021-04-04 00:21:59 +02:00
Sergey G. Brester ff09c52927 Merge pull request #2881 from stepodev/master
`filter.d/nginx-http-auth.conf` - extended with parameter mode, so additionally to `auth` (or `normal`)  mode `fallback` (or combined as `aggressive`) can find SSL errors while SSL handshaking
2021-04-04 00:07:03 +02:00
Sergey G. Brester dda70d60c0 Merge branch 'master' into master 2021-04-04 00:04:08 +02:00
Sergey G. Brester c5d43d7573 Update ChangeLog 2021-04-04 00:00:59 +02:00
Michele Mondelli 7579072e3b docs: fix typos 2021-04-03 23:49:23 +02:00
Sergey G. Brester 4eba9f2a4b Merge pull request #2950 from sunweaver/pr/scanlogd-filter
Add support for filtering out detected port scans via scanlogd.
2021-04-03 23:36:14 +02:00
Sergey G. Brester 2d51240b3e correction for default log interpolation and added allports banaction 2021-04-03 23:33:49 +02:00
Sergey G. Brester 977dfe4bd7 small amend: sport after saddr is optional
format of message: saddr[:sport] to daddr [and others,] ports port[, port...], ..., flags[, TOS TOS][, TTL TTL] @HH:MM:SS
2021-04-03 23:29:16 +02:00
Sergey G. Brester 14edeed310 fixed regex (don't need to match whole line, e. g. every port etc) 2021-04-03 23:24:55 +02:00
Sergey G. Brester 0c4d356d11 added test log-file 2021-04-03 23:10:51 +02:00
Sergey G. Brester 080dd12288 Merge pull request #2965 from oukb/patch-1
nsd.conf: fix for the current log format
2021-04-03 21:02:03 +02:00
Sergey G. Brester a838deba7f restore anchor (e. g. catch all in the middle), dot is optional now, RE rewritten a bit more precise 2021-04-03 21:00:14 +02:00
Sergey G. Brester 1215cb28ac Update nsd 2021-04-03 20:58:26 +02:00
sebres d445b5671d Merge pull request #2642 from rolschewsky/mssql (and amend from sebres/mssql) 2021-04-03 20:28:18 +02:00
sebres 7f38b80d35 precise regex (left anchor and fewer catch-all's); fixed tests (added failJSON and more tests for some corner-cases around new RE) 2021-04-03 20:16:47 +02:00
Rüdiger Olschewsky 9eaa2322b0 Filter and Defaults for Microsoft SQL Server 2021-04-03 19:30:29 +02:00
Markus Felten 5aa20c30d8 fix: add journalmatch to nginx filters 2021-04-03 19:20:50 +02:00
j-marz 5d8f500471 updated formatting to pass tests 2021-03-29 08:36:53 +11:00
j-marz 2686811593 Updated zoneminder filter
Support new log format, ERR instead of WAR. Add detection of non-existent user login attempts
2021-03-28 21:19:10 +11:00
sebres 80a33b1dee Merge branch '0.11' 2021-03-25 12:14:11 +01:00
sebres b259e81911 test-suite: skip testFQDN if no network 2021-03-25 12:13:46 +01:00
sebres d8e450cf12 Merge branch 'fix-readline-multibyte' 2021-03-25 12:13:18 +01:00
sebres 4b17dddc23 update ChangeLog 2021-03-25 12:07:34 +01:00
sebres ccf4f3a07d amend with common log-file iterator in fail2ban-regex and test-suite (in sample regex factory also) 2021-03-25 12:07:31 +01:00
sebres 9659033523 fail2ban-regex: reimplemented log-file iterator - uses FileContainer facilities now instead of direct read from file and decode;
fail2banregextestcase.py extended to cover proper line-ending handling by interim NL char as part of multi-byte encodings (utf-16be, utf-16le)
2021-03-25 12:07:29 +01:00
sebres cbac7c176a readline fixed to consider interim new-line character as part of code point in multi-byte logs (e. g. unicode: utf-16be, utf-16le);
suppress warning "Error decoding line" for incomplete line (produced by not fully read multi-byte new-line character at end of data);
added test coverage for such logs
2021-03-25 12:07:26 +01:00
sebres 6cf4669dee Merge branch '0.10' into 0.11 2021-03-24 14:18:22 +01:00
sebres d135aeea16 fixes restore of original logging withing tests (LogCaptureTestCase.tearDown) - python 3 seemed still to log wordy after tear down (setting of log.level does not restore the level for related log objects - e. g. for logger of fail2ban.jail etc, so fail2ban-testcases '(testVersion|testLongName).*servertest' generating messages in stdout handler in testLongName) 2021-03-24 14:14:47 +01:00
sebres 8757563be1 close fork 2021-03-23 14:20:10 +01:00
sebres 996920cdaa in operation mode the filter reads only complete lines (ended with new-line) now, otherwise it would wait for end of line (for its completion) 2021-03-22 01:17:26 +01:00
sebres 061fab898a Merge branch '0.10' into 0.11 2021-03-22 00:58:03 +01:00
sebres e587526ede tests: add missing constraint (causing incomplete comparison in below cycle if fewer lines as expected was found) 2021-03-22 00:56:40 +01:00
sebres 343ccd7e8a small optimization 2021-03-21 23:35:38 +01:00
sebres 9bdc4be6cc stability: better recognition of rotation (e. g. on hash collision, consider current size and last known position now), no hash of empty file (or not fulfilled line), etc;
performance: avoid unnecessary seek to start of file and hash calculation - now it occurs only if file really rotated (ino changing or size shrinking), otherwise not earlier than in 30 seconds;
avoid unneeded log-rotation in tests
2021-03-21 23:35:09 +01:00
sebres 725354c793 action info extended with new members for jail info (usable as tags in command actions):
`jail.found`, `jail.found_total` - current and total found failures
  `jail.banned`, `jail.banned_total` - current and total bans
closes #10
2021-03-20 22:33:31 +01:00
oukb 529866b2bb nsd.conf: fix for the current log format
New nsd 4.3.5 log format:

|  [2021-03-05 05:25:14.562] nsd[160800]: info: axfr for example.com. from 192.35.168.32 refused, no acl matches
|  [2021-03-06 05:24:33.223] nsd[356033]: info: axfr for localhost. from 192.35.168.160 refused, no acl matches
|  [2021-03-07 05:23:26.641] nsd[547893]: info: axfr for example.com. from 192.35.168.64 refused, no acl matches
|  [2021-03-08 05:18:54.067] nsd[739606]: info: axfr for example.com. from 192.35.168.32 refused, no acl matches
2021-03-08 19:14:28 +03:00
Mike Gabriel f15ed35619 config/: Add support for filtering out detected port scans via scanlogd. 2021-03-05 16:35:13 +01:00
Sergey G. Brester 08393f9d82 Update filter_request.md 2021-03-03 20:28:27 +01:00
sebres df5e024fb8 new issue templates 2021-03-03 20:16:34 +01:00
sebres fb08534ed7 Merge branch '0.11' 2021-03-03 18:17:35 +01:00
sebres 3eaefe8da0 Merge branch '0.10' into 0.11 2021-03-03 18:16:47 +01:00
sebres 04aba6168c fixed typo, -- is not expected in options declaration, so --dump-pretty did never work (only --dp is working) 2021-03-03 13:02:00 +01:00
sebres a45b1c974c filter.d/ignorecommands/apache-fakegooglebot: added timeout parameter (default 55 seconds) - avoid fail with timeout (default 1 minute) by reverse lookup on some slow DNS services (googlebots must be resolved fast);
closes gh-2951
2021-03-02 19:35:27 +01:00
sebres 63acc862b1 action.d/nginx-block-map.conf: reload nginx only if it is running (also avoid error in nginx-errorlog, gh-2949) and better test coverage for the action 2021-02-24 18:21:42 +01:00
sebres fb6315ea5e Merge branch '0.10' into 0.11 2021-02-24 13:16:36 +01:00
sebres 6f4b6ec8cc action.d/badips.* removed (badips.com is no longer active, gh-2889) 2021-02-24 13:05:04 +01:00
sebres e3d43d1241 Merge branch 'fix-rc-on-too-many-failures' into 0.10: resolves RC with uncontrolled growth of failure list (jail with too many matches that did not cause ban, gh-2945) 2021-02-24 12:45:15 +01:00
sebres 92a2242174 amend fixing journal tests (systemd backend only) 2021-02-23 15:54:48 +01:00
sebres e353fb8024 fixed test cases (ban ASAP also followed in test suite now, so failure reached maxretry causes immediate ban now) 2021-02-23 02:46:44 +01:00
sebres 55d7d9e214 *WiP* try to solve RC on jails with too many failures without ban, gh-2945 ... 2021-02-22 18:39:58 +01:00
sebres 884cbbd6e1 Merge branch '0.11' 2021-02-17 19:04:23 +01:00
sebres abc5a4e062 ChangeLog (#2742) 2021-02-17 19:02:22 +01:00
sebres a0352182e8 Merge branch '0.10' into 0.11 2021-02-17 18:57:38 +01:00
sebres 294ec73f62 Merge branch 'py-3-10-alpha-5' into 0.10 2021-02-17 18:49:06 +01:00
Sergey G. Brester 9f1d1f4fbd amend for Mapping (jails) 2021-02-17 18:47:42 +01:00
Sergey G. Brester 42dee38ad2 amend for Mapping 2021-02-17 18:47:40 +01:00
Sergey G. Brester 2b6bb2c1be follow bpo-37324: :ref:collections-abstract-base-classes moved to the :mod:collections.abc module
(since 3.10-alpha.5 `MutableMapping` is missing in collections module)
2021-02-17 18:47:38 +01:00
Sergey G. Brester 8ae9208454 try to provide coverage for 3.10-alpha.5 (#2931) 2021-02-17 18:47:32 +01:00
Sergey G. Brester a2f0dbad87 Merge pull request #2742 from aresxc/patch-1
Update  drupal-auth.conf
2021-02-11 19:10:55 +01:00
Sergey G. Brester f4f92aa72d more tests covering different cases, injections attempt etc 2021-02-11 18:56:53 +01:00
Sergey G. Brester d678440658 more precise RE (avoids weakness with catch-all's and is injection safe) 2021-02-11 18:32:32 +01:00
sebres ea26509594 Merge branch '0.11' 2021-02-03 14:59:00 +01:00
sebres 6198b4566c Merge branch '0.10' into 0.11 2021-02-03 14:47:56 +01:00
sebres 366c64cb9d extractOptions: ensure options are parsed completely - avoids unexpected skip or truncate of parameters, produces more verbose error message in case of incorrect syntax; added more tests covering several cases
WARN: potential incompatibility (since it doesn't silently ignore wrong syntax anymore)
2021-02-03 14:45:30 +01:00
sebres 409348394e Merge pull request #2913
Add transport to asterisk RE
2021-02-03 12:41:29 +01:00
Brian J. Murrell 7f185a828e Update date in failJSON
The date format in failJSON is specific, so convert the date to use that format.
2021-01-31 15:22:16 +01:00
Brian J. Murrell 69c96c00c0 Log entries for updated failregex
Add a sample failregex.

Signed-off-by: Brian J. Murrell <brian@interlinx.bc.ca>
2021-01-31 15:22:16 +01:00
Brian J. Murrell dc4ee5aa47 Add transport to asterisk RE
Call rejection messages from Asterisk can have the transport prefixed to the IP address.

Signed-off-by: Brian J. Murrell <brian@interlinx.bc.ca>
2021-01-31 15:22:16 +01:00
sebres c75748c5d3 fail2ban.conf: added new fail2ban configuration option "allowipv6" (default auto), can be used to allow or disallow IPv6 interface in fail2ban immediately by start (e. g. if fail2ban starts before network interfaces).
closes gh-2804
2021-01-27 17:06:14 +01:00
sebres 3700a9e523 invalidate IP/DNS caches by reload, so inter alia would allow to recognize IPv6IsAllowed immediately, previously retarded up to cache max-time (5m);
closes gh-2804
2021-01-26 20:35:14 +01:00
sebres 21dd317870 Merge branch '0.11' 2021-01-21 19:13:13 +01:00
sebres dbc77c47c3 Merge branch '0.10' into 0.11 2021-01-21 19:11:01 +01:00
sebres 913c37db80 more fixes and optimizations, better RE's for patterns, allow parse date without time with such a datepattern (assume 00:00:00 then), etc 2021-01-21 19:00:56 +01:00
sebres 0f44a3408a amend to 747d468322:
fail2ban-regex: loosen up date patterns %ExY, %Exy - let accept every year from 19xx up to current century (+3 years)
2021-01-21 19:00:53 +01:00
Sergey G. Brester 164105fab1 added new parameter namespace for systemd backend
closes gh-2910
2021-01-16 17:10:12 +01:00
Sergey G. Brester 5f3f4d1e2f action.d/cloudflare.conf: better IPv6 capability
closes gh-2891
2021-01-11 15:23:40 +01:00
sebres 9df332fdef filter.d/apache-overflows.conf: extended to match AH00126 error (Invalid URI ...);
closes gh-2908
2021-01-11 15:10:53 +01:00
Michael Haro 2781e7b9d1 Add blank line so markdown renders correctly 2021-01-09 13:38:33 +01:00
Sergey G. Brester 2d4c3edeb6 Merge pull request #2906 from fail2ban/ci-py-3.10
CI: add python 3.10 (alpha) to github actions
2021-01-06 14:45:11 +01:00
Sergey G. Brester c9907bef0d CI: 3.10.0-alpha.4 2021-01-06 14:37:34 +01:00
Sergey G. Brester 097d9ea7fe try wildcard (3.10 is still alpha) 2021-01-06 14:36:09 +01:00
Sergey G. Brester 3c8aa0e6ba amend with correct yaml notation (float vs string) 2021-01-06 14:34:08 +01:00
Sergey G. Brester 0a7f5b7b5c CI: add python 3.10 to github actions 2021-01-06 14:29:18 +01:00
sebres e60b10392d Merge branch '0.11' into master 2021-01-04 02:50:33 +01:00
sebres f259dac747 Merge branch '0.10' into 0.11 2021-01-04 02:48:49 +01:00
sebres 747d468322 fixes century selector of %ExY and %Exy in datepattern for tests, considering interval from 2005 (alternate now) to now; + better grouping algorithm for resulting century RE 2021-01-04 02:45:16 +01:00
sebres 2c60d08b28 Merge '0.11' (fix gh-2899) into master 2020-12-29 21:27:02 +01:00
sebres fe334590cd Merge branch '0.10' into 0.11 2020-12-29 21:25:09 +01:00
sebres 73b39e0894 filter.d/named-refused.conf: fixes prefix for messages from systemd journal (no mandatory space ahead, because don't have timestamp)
closes gh-2899
2020-12-29 21:22:47 +01:00
Sergey G. Brester 6b5cc2dee8 Merge pull request #2898 from defanor/postscreen-pregreet-hangup
Handle postscreen's PREGREET and HANGUP messages in ddos and aggressive mode
2020-12-29 13:21:47 +01:00
defanor ba7daef86c Handle postscreen's PREGREET and HANGUP messages
Provoking those seems to be a popular activity among spammers.
2020-12-24 17:29:09 +03:00
Tim Gates 3bb19ecc9d docs: fix simple typo, litle -> little
There is a small typo in fail2ban/client/fail2banclient.py, fail2ban/server/observer.py.

Should read `little` rather than `litle`.
2020-12-23 12:59:03 +01:00
sebres 7d256681a4 Merge branch '0.11' into master 2020-12-22 20:41:41 +01:00
sebres 567039f261 Merge branch '0.10' into 0.11 2020-12-22 20:40:52 +01:00
sebres 27e435a7f5 fix cymru test cases 2020-12-22 20:36:01 +01:00
stepodev cecc3d62ff add mode explanation to nginx-http-auth in jail.conf 2020-11-30 12:26:32 +01:00
stepodev d0ba27cf46 move nginx-tls-fallback rules to nginx-http-auth 2020-11-30 12:14:49 +01:00
Sergey G. Brester d959f6d199 Update nginx-tls-fallback.conf
more precise and conclusive regex without catch-all's
2020-11-26 12:25:32 +01:00
stepodev c0256724a7 fix monitoring wrong error log. was access log, should be error.log 2020-11-25 21:30:21 +01:00
stepodev 27c40a77a3 add nginx-tls-downgrade 2020-11-25 20:59:43 +01:00
sebres b8e8a87ee9 small amend to 0f27a8add8 - datedetector, strptime: token %Z recognizes all known zone abbreviation besides Z, GMT, UTC correctly, if it is matching (%z remains unchanged for backwards-compatibility, see comment in code); test cases fixed (PDT zone will be found now). 2020-11-24 20:23:39 +01:00
sebres 0f27a8add8 datedetector, strptime: token %Z must recognize zone abbreviation Z (GMT/UTC) also, similar to %z;
more test cases added.
2020-11-24 19:28:46 +01:00
sebres a03109d096 Merge branch '0.11' into master (0.11.2 released) 2020-11-24 12:41:10 +01:00
Sergey G. Brester 753fff9c15 amend to #2750, add jail for new filter nginx-bad-request 2020-11-23 18:38:41 +01:00
Sergey G. Brester 071048b8f2 Merge pull request #2750 from janprzy/master
Added filter nginx-bad-request
2020-11-23 18:28:07 +01:00
sebres f5ea40c7da Merge branch '0.11' into master 2020-11-11 11:19:05 +01:00
sebres 9f635c66fb Merge branch '0.11' into master 2020-11-09 12:45:54 +01:00
sebres 690ad20958 Merge branch '0.11' into master 2020-11-05 16:16:17 +01:00
Sergey G. Brester 9d77fb2b4c 1st try of GH actions flow (CI only, no coverage atm) 2020-11-03 15:38:06 +01:00
sebres 960e30cfcd Merge branch '0.11' 2020-09-23 19:41:04 +02:00
Sergey G. Brester 70c601e9e5 involve config parameter (replaces hard-coded path); fixed typo in actionban (looks like copy&paste from trimmed tty) 2020-09-02 20:47:05 +02:00
sebres 8bc7623388 Merge branch '0.11' 2020-08-26 13:49:41 +02:00
Sergey G. Brester 57caf8ec90 Merge pull request #2768 from TorontoMedia/patch
Remove duplicate method and rename invalid parameter
2020-08-24 16:39:25 +02:00
sebres 7327fee2c8 Merge branch '0.11' 2020-08-24 16:33:30 +02:00
Chris Caron 2216fd8da4 Add Apprise Support (50+ Notifications) 2020-08-04 19:04:05 -04:00
sebres 1ea36c3045 Merge branch '0.11' 2020-08-04 17:19:51 +02:00
TorontoMedia fd25c4cbb8 Remove duplicate method and rename invalid parameter 2020-06-28 12:58:41 -04:00
Jan Przybylak 56fefe9240 Added test file "nginx-bad-request"
I tested with `./fail2ban-testcases testSampleRegex`, which did not return any errors.
2020-06-21 18:25:27 +02:00
Jan Przybylak a5ab4406d8 Removed unnecessary escape sequence
This commit also contains changes to match requests that are 100% empty (by using "*" instead of "+" in the regex)
2020-06-21 18:24:09 +02:00
Jan Przybylak d7ef5d166d Removed vulnerable catchall & anchor 2020-06-11 16:44:48 +02:00
sebres 78fc99ec7e Merge branch '0.11' 2020-06-11 12:52:34 +02:00
Jan Przybylak 3c83c19070 Added filter nginx-bad-request 2020-06-06 19:51:46 +02:00
aresdr 412120ac3c Update drupal-auth.conf
Small fix for Drupal 8. D8 uses "Login attempt failed from" while D7 uses "Login attempt failed for".
The referer part is a must currently, but some requests did not have one and are not failing.
2020-05-30 15:25:31 -07:00
sebres 6cff2bb007 Merge branch '0.11' 2020-05-25 19:09:29 +02:00
Jordi Sanfeliu ede2009708 added new jail (and filter) Monitorix 2020-04-03 12:52:19 +02:00
sebres 6e570b8644 Merge branch '0.11' 2020-03-13 23:23:32 +01:00
sebres 9905904bba Merge branch '0.11' 2020-03-13 22:43:22 +01:00
sebres 4c22d4a801 Merge branch '0.11' 2020-03-13 17:47:03 +01:00
sebres 8b43d54878 Merge branch '0.11' 2020-03-05 14:32:42 +01:00
sebres ef1eaf9b37 Merge branch '0.11' 2020-02-25 17:18:50 +01:00
sebres 35591db3e8 Merge branch '0.11' 2020-02-14 12:14:51 +01:00
sebres 65ce64ba6d Merge branch '0.11' 2020-02-07 13:57:00 +01:00
Mihail Politaev 303861d7c7 Using native firewalld ipset implementation
By creating additional action file firewallcmd-ipset-native.conf
2020-01-30 21:17:32 +02:00
sebres ccbc0c7405 Merge branch '0.11' 2020-01-30 10:23:14 +01:00
sebres 7f1b578af4 Merge branch 'gh-488-check-on-error' 2020-01-16 16:52:44 +01:00
sebres 8cbc1e0ebb ChangeLog (change actioncheck behavior) 2020-01-16 16:51:57 +01:00
sebres 546fff8dc0 Merge branch '0.11' 2020-01-16 16:25:20 +01:00
sebres ed4b5b8bdc change actioncheck behavior (on error only, gh-488) 2020-01-15 14:54:31 +01:00
sebres 94b64055e1 Merge branch '0.11' 2020-01-15 14:53:28 +01:00
Sergey G. Brester 3f5c382a98 readme: amend to bump version (v.1.0.1 is master now) 2020-01-14 20:55:24 +01:00
sebres bb0f732ae6 version bump (master is 1.0.x-dev now) 2020-01-14 20:38:26 +01:00
120 changed files with 1954 additions and 1267 deletions
+1
View File
@@ -0,0 +1 @@
ChangeLog linguist-language=Markdown
-49
View File
@@ -1,49 +0,0 @@
_We will be very grateful, if your problem was described as completely as possible,
enclosing excerpts from logs (if possible within DEBUG mode, if no errors evident
within INFO mode), and configuration in particular of effected relevant settings
(e.g., with ` fail2ban-client -d | grep 'affected-jail-name' ` for a particular
jail troubleshooting).
Thank you in advance for the details, because such issues like "It does not work"
alone could not help to resolve anything!
Thanks! (remove this paragraph and other comments upon reading)_
### Environment:
_Fill out and check (`[x]`) the boxes which apply. If your Fail2Ban version is outdated,
and you can't verify that the issue persists in the recent release, better seek support
from the distribution you obtained Fail2Ban from_
- Fail2Ban version (including any possible distribution suffixes):
- OS, including release name/version:
- [ ] Fail2Ban installed via OS/distribution mechanisms
- [ ] You have not applied any additional foreign patches to the codebase
- [ ] Some customizations were done to the configuration (provide details below is so)
### The issue:
_Summary here_
#### Steps to reproduce
#### Expected behavior
#### Observed behavior
#### Any additional information
### Configuration, dump and another helpful excerpts
#### Any customizations done to /etc/fail2ban/ configuration
```
```
#### Relevant parts of /var/log/fail2ban.log file:
_preferably obtained while running fail2ban with `loglevel = 4`_
```
```
#### Relevant lines from monitored log files in question:
```
```
+70
View File
@@ -0,0 +1,70 @@
---
name: Bug report
about: Report a bug within the fail2ban engines (not filters or jails)
title: '[BR]: '
labels: bug
assignees: ''
---
<!--
- Before reporting, please make sure to search the open and closed issues for any reports in the past.
- Use this issue template to report a bug in the fail2ban engine (not in a filter or jail).
- If you want to request a feature or a new filter, please use "Feature request" or "Filter request" instead.
- If you have rather some question, please open or join to some discussion.
We will be very grateful, if your problem was described as completely as possible,
enclosing excerpts from logs (if possible within DEBUG mode, if no errors evident
within INFO mode), and configuration in particular of effected relevant settings
(e.g., with ` fail2ban-client -d | grep 'affected-jail-name' ` for a particular
jail troubleshooting).
Thank you in advance for the details, because such issues like "It does not work"
alone could not help to resolve anything!
Thanks!
(you can remove this paragraph and other comments upon reading)
-->
### Environment:
<!--
Fill out and check (`[x]`) the boxes which apply. If your Fail2Ban version is outdated,
and you can't verify that the issue persists in the recent release, better seek support
from the distribution you obtained Fail2Ban from
-->
- Fail2Ban version <!-- including any possible distribution suffixes --> :
- OS, including release name/version :
- [ ] Fail2Ban installed via OS/distribution mechanisms
- [ ] You have not applied any additional foreign patches to the codebase
- [ ] Some customizations were done to the configuration (provide details below is so)
### The issue:
<!-- summary here -->
#### Steps to reproduce
#### Expected behavior
#### Observed behavior
#### Any additional information
### Configuration, dump and another helpful excerpts
#### Any customizations done to /etc/fail2ban/ configuration
<!-- put your configuration excerpts between next 2 lines -->
```
```
#### Relevant parts of /var/log/fail2ban.log file:
<!-- preferably obtained while running fail2ban with `loglevel = 4` -->
<!-- put your log excerpt between next 2 lines -->
```
```
#### Relevant lines from monitored log files:
<!-- put your log excerpt between next 2 lines -->
```
```
+35
View File
@@ -0,0 +1,35 @@
---
name: Feature request
about: Suggest an idea or an enhancement for this project
title: '[RFE]: '
labels: enhancement
assignees: ''
---
<!--
- Before requesting, please make sure to search the open and closed issues for any requests in the past.
- Use this issue template to request a feature in the fail2ban engine (not a new filter or jail).
- If you want to request a new filter or failregex, please use "Filter request" instead.
- If you have rather some question, please open or join to some discussion.
-->
#### Feature request type
<!--
Please provide a summary description of the feature request.
-->
#### Description
<!--
Please describe the feature in more detail.
-->
#### Considered alternatives
<!--
A clear and concise description of any alternative solutions or features you've considered.
-->
#### Any additional information
<!--
Add any other context or screenshots about the feature request here.
-->
+59
View File
@@ -0,0 +1,59 @@
---
name: Filter request
about: Request a new jail or filter to be supported or existing filter extended with new failregex
title: '[FR]: '
labels: filter-request
assignees: ''
---
<!--
- Before requesting, please make sure to search the open and closed issues for any requests in the past.
- Sometimes failregex have been already requested before but are not implemented yet due to various reasons.
- If there are no hits for your concerns, please proceed otherwise add a comment to the related issue (also if it is closed).
- If you want to request a new feature, please use "Feature request" instead.
- If you have rather some question, please open or join to some discussion.
-->
### Environment:
<!--
Fill out and check (`[x]`) the boxes which apply.
-->
- Fail2Ban version <!-- including any possible distribution suffixes --> :
- OS, including release name/version :
#### Service, project or product which log or journal should be monitored
- Name of filter or jail in Fail2Ban (if already exists) :
- Service, project or product name, including release name/version :
- Repository or URL (if known) :
- Service type :
- Ports and protocols the service is listening :
#### Log or journal information
<!-- Delete unrelated group -->
<!-- Log file -->
- Log file name(s) :
<!-- Systemd journal -->
- Journal identifier or unit name :
#### Any additional information
### Relevant lines from monitored log files:
#### failures in sense of fail2ban filter (fail2ban must match):
<!-- put your log excerpt between next 2 lines -->
```
```
#### legitimate messages (fail2ban should not consider as failures):
<!-- put your log excerpt between next 2 lines -->
```
```
+2 -1
View File
@@ -1,7 +1,8 @@
Before submitting your PR, please review the following checklist:
- [ ] **CHOOSE CORRECT BRANCH**: if filing a bugfix/enhancement
against 0.9.x series, choose `master` branch
against certain release version, choose `0.9`, `0.10` or `0.11` branch,
for dev-edition use `master` branch
- [ ] **CONSIDER adding a unit test** if your PR resolves an issue
- [ ] **LIST ISSUES** this PR resolves
- [ ] **MAKE SURE** this PR doesn't break existing tests
+44 -12
View File
@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, pypy2, pypy3]
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, '3.10', '3.11.0-alpha.1', pypy2, pypy3]
fail-fast: false
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
@@ -33,34 +33,66 @@ jobs:
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Grant systemd-journal access
run: sudo usermod -a -G systemd-journal "$USER" || echo 'no systemd-journal access'
- name: Python version
run: |
F2B_PY=$(python -c "import sys; print(sys.version)")
echo "Python: ${{ matrix.python-version }} -- $F2B_PY"
echo "Python: ${{ matrix.python-version }} -- ${F2B_PY/$'\n'/ }"
F2B_PYV=$(echo "${F2B_PY}" | grep -oP '^\d+(?:\.\d+)')
F2B_PY=${F2B_PY:0:1}
echo "Set F2B_PY=$F2B_PY"
echo "Set F2B_PY=$F2B_PY, F2B_PYV=$F2B_PYV"
echo "F2B_PY=$F2B_PY" >> $GITHUB_ENV
echo "F2B_PYV=$F2B_PYV" >> $GITHUB_ENV
# for GHA we need to monitor all journals, since it cannot be found using SYSTEM_ONLY(4):
echo "F2B_SYSTEMD_DEFAULT_FLAGS=0" >> $GITHUB_ENV
- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [[ "$F2B_PY" = 3 ]]; then python -m pip install --upgrade pip || echo "can't upgrade pip"; fi
if [[ "$F2B_PY" = 3 ]] && ! command -v 2to3x -v 2to3 > /dev/null; then
pip install 2to3
#pip install 2to3
sudo apt-get -y install 2to3
fi
pip install systemd-python || echo 'systemd not available'
pip install pyinotify || echo 'inotify not available'
#sudo apt-get -y install python${F2B_PY/2/}-pyinotify || echo 'inotify not available'
python -m pip install pyinotify || echo 'inotify not available'
#sudo apt-get -y install python${F2B_PY/2/}-systemd || echo 'systemd not available'
sudo apt-get -y install libsystemd-dev || echo 'systemd dependencies seems to be unavailable'
python -m pip install systemd-python || echo 'systemd not available'
- name: Before scripts
run: |
cd "$GITHUB_WORKSPACE"
# Manually execute 2to3 for now
if [[ "$F2B_PY" = 3 ]]; then echo "2to3 ..." && ./fail2ban-2to3; fi
_debug() { echo -n "$1 "; err=$("${@:2}" 2>&1) && echo 'OK' || echo -e "FAIL\n$err"; }
# (debug) output current preferred encoding:
python -c 'import locale, sys; from fail2ban.helpers import PREFER_ENC; print(PREFER_ENC, locale.getpreferredencoding(), (sys.stdout and sys.stdout.encoding))'
_debug 'Encodings:' python -c 'import locale, sys; from fail2ban.helpers import PREFER_ENC; print(PREFER_ENC, locale.getpreferredencoding(), (sys.stdout and sys.stdout.encoding))'
# (debug) backend availabilities:
echo 'Backends:'
_debug '- systemd:' python -c 'from fail2ban.server.filtersystemd import FilterSystemd'
#_debug '- systemd (root): ' sudo python -c 'from fail2ban.server.filtersystemd import FilterSystemd'
_debug '- pyinotify:' python -c 'from fail2ban.server.filterpyinotify import FilterPyinotify'
- name: Test suite
run: if [[ "$F2B_PY" = 2 ]]; then python setup.py test; else python bin/fail2ban-testcases --verbosity=2; fi
run: |
if [[ "$F2B_PY" = 2 ]]; then
python setup.py test
elif dpkg --compare-versions "$F2B_PYV" lt 3.10; then
python bin/fail2ban-testcases --verbosity=2
else
echo "Skip systemd backend since systemd-python module must be fixed for python >= v.3.10 in GHA ..."
python bin/fail2ban-testcases --verbosity=2 -i "[sS]ystemd|[jJ]ournal"
fi
#- name: Test suite (debug some systemd tests only)
#run: python bin/fail2ban-testcases --verbosity=2 "[sS]ystemd|[jJ]ournal"
#run: python bin/fail2ban-testcases --verbosity=2 -l 5 "test_WrongChar"
- name: Build
run: python setup.py build
#- name: Test initd scripts
# run: shellcheck -s bash -e SC1090,SC1091 files/debian-initd
+1 -9
View File
@@ -10,16 +10,8 @@ dist: xenial
matrix:
fast_finish: true
include:
- python: 2.6
dist: trusty # required for Python 2.6
- python: 2.7
dist: trusty # required for packages like gamin
name: 2.7 (trusty)
- python: 2.7
name: 2.7 (xenial)
- python: pypy
- python: 3.3
dist: trusty
#- python: pypy
- python: 3.4
- python: 3.5
- python: 3.6
+34 -1
View File
@@ -6,6 +6,39 @@
Fail2Ban: Changelog
===================
ver. 1.0.1-dev-1 (20??/??/??) - development nightly edition
-----------
### Compatibility:
* potential incompatibility by parsing of options of `backend`, `filter` and `action` parameters (if they
are partially incorrect), because fail2ban could throw an error now (doesn't silently bypass it anymore).
* to v.0.11:
- due to change of `actioncheck` behavior (gh-488), some actions can be incompatible as regards
the invariant check, if `actionban` or `actionunban` would not throw an error (exit code
different from 0) in case of unsane environment.
### Fixes
* readline fixed to consider interim new-line character as part of code point in multi-byte logs
(e. g. unicode encoding like utf-16be, utf-16le);
* `action.d/ufw.conf`:
- fixed handling on IPv6 (using prepend, gh-2331, gh-3018)
- application names containing spaces can be used now (gh-656, gh-1532, gh-3018)
* `filter.d/drupal-auth.conf` more strict regex, extended to match "Login attempt failed from" (gh-2742)
### New Features and Enhancements
* `actioncheck` behavior is changed now (gh-488), so invariant check as well as restore or repair
of sane environment (in case of recognized unsane state) would only occur on action errors (e. g.
if ban or unban operations are exiting with other code as 0)
* better recognition of log rotation, better performance by reopen: avoid unnecessary seek to begin of file
(and hash calculation)
* file filter reads only complete lines (ended with new-line) now, so waits for end of line (for its completion)
* `action.d/ufw.conf` (gh-3018):
- new option `add` (default `prepend`), can be supplied as `insert 1` for ufw versions before v.0.36 (gh-2331, gh-3018)
- new options `kill-mode` and `kill` to drop established connections of intruder (see action for details, gh-3018)
* `filter.d/nginx-http-auth.conf` - extended with parameter mode, so additionally to `auth` (or `normal`)
mode `fallback` (or combined as `aggressive`) can find SSL errors while SSL handshaking, gh-2881
ver. 0.11.2 (2020/11/23) - heal-the-world-with-security-tools
-----------
@@ -189,7 +222,7 @@ Yes, Hrrrm...
### New Features
* new replacement tags for failregex to match subnets in form of IP-addresses with CIDR mask (gh-2559):
- `<CIDR>` - helper regex to match CIDR (simple integer form of net-mask);
- `<SUBNET>` - regex to match sub-net adresses (in form of IP/CIDR, also single IP is matched, so part /CIDR is optional);
- `<SUBNET>` - regex to match sub-net addresses (in form of IP/CIDR, also single IP is matched, so part /CIDR is optional);
* grouped tags (`<ADDR>`, `<HOST>`, `<SUBNET>`) recognize IP addresses enclosed in square brackets
* new failregex-flag tag `<F-MLFGAINED>` for failregex, signaled that the access to service was gained
(ATM used similar to tag `<F-NOFAIL>`, but it does not add the log-line to matches, gh-2279)
+1
View File
@@ -278,6 +278,7 @@ to tune it. fail2ban-regex -D ... will present Debuggex URLs for the regexs
and sample log files that you pass into it.
In general use when using regex debuggers for generating fail2ban filters:
* use regex from the ./fail2ban-regex output (to ensure all substitutions are
done)
* replace <HOST> with (?&.ipv4)
+1 -3
View File
@@ -3,10 +3,9 @@ bin/fail2ban-regex
bin/fail2ban-server
bin/fail2ban-testcases
ChangeLog
config/action.d/apprise.conf
config/action.d/abuseipdb.conf
config/action.d/apf.conf
config/action.d/badips.conf
config/action.d/badips.py
config/action.d/blocklist_de.conf
config/action.d/bsd-ipfw.conf
config/action.d/cloudflare.conf
@@ -220,7 +219,6 @@ fail2ban/setup.py
fail2ban-testcases-all
fail2ban-testcases-all-python3
fail2ban/tests/action_d/__init__.py
fail2ban/tests/action_d/test_badips.py
fail2ban/tests/action_d/test_smtp.py
fail2ban/tests/actionstestcase.py
fail2ban/tests/actiontestcase.py
+8 -7
View File
@@ -2,7 +2,7 @@
/ _|__ _(_) |_ ) |__ __ _ _ _
| _/ _` | | |/ /| '_ \/ _` | ' \
|_| \__,_|_|_/___|_.__/\__,_|_||_|
v0.11.0.dev1 20??/??/??
v1.0.1.dev1 20??/??/??
## Fail2Ban: ban hosts that cause multiple authentication errors
@@ -34,6 +34,7 @@ this case, you should use that instead.**
Required:
- [Python2 >= 2.6 or Python >= 3.2](https://www.python.org) or [PyPy](https://pypy.org)
- python-setuptools, python-distutils or python3-setuptools for installation from source
Optional:
- [pyinotify >= 0.8.3](https://github.com/seb-m/pyinotify), may require:
@@ -46,11 +47,11 @@ Optional:
To install:
tar xvfj fail2ban-0.11.0.tar.bz2
cd fail2ban-0.11.0
tar xvfj fail2ban-1.0.1.tar.bz2
cd fail2ban-1.0.1
sudo python setup.py install
Alternatively, you can clone the source from GitHub to a directory of Your choice, and do the install from there. Pick the correct branch, for example, 0.11
Alternatively, you can clone the source from GitHub to a directory of Your choice, and do the install from there. Pick the correct branch, for example, master or 0.11
git clone https://github.com/fail2ban/fail2ban.git
cd fail2ban
@@ -89,11 +90,11 @@ fail2ban(1) and jail.conf(5) manpages for further references.
Code status:
------------
* travis-ci.org: [![tests status](https://secure.travis-ci.org/fail2ban/fail2ban.svg?branch=0.11)](https://travis-ci.org/fail2ban/fail2ban?branch=0.11) (0.11 branch) / [![tests status](https://secure.travis-ci.org/fail2ban/fail2ban.svg?branch=0.10)](https://travis-ci.org/fail2ban/fail2ban?branch=0.10) (0.10 branch)
* travis-ci.org: [![tests status](https://secure.travis-ci.org/fail2ban/fail2ban.svg?branch=master)](https://travis-ci.org/fail2ban/fail2ban?branch=master) / [![tests status](https://secure.travis-ci.org/fail2ban/fail2ban.svg?branch=0.11)](https://travis-ci.org/fail2ban/fail2ban?branch=0.11) (0.11 branch) / [![tests status](https://secure.travis-ci.org/fail2ban/fail2ban.svg?branch=0.10)](https://travis-ci.org/fail2ban/fail2ban?branch=0.10) (0.10 branch)
* coveralls.io: [![Coverage Status](https://coveralls.io/repos/fail2ban/fail2ban/badge.svg?branch=0.11)](https://coveralls.io/github/fail2ban/fail2ban?branch=0.11) (0.11 branch) / [![Coverage Status](https://coveralls.io/repos/fail2ban/fail2ban/badge.svg?branch=0.10)](https://coveralls.io/github/fail2ban/fail2ban?branch=0.10) / (0.10 branch)
* coveralls.io: [![Coverage Status](https://coveralls.io/repos/fail2ban/fail2ban/badge.svg?branch=master)](https://coveralls.io/github/fail2ban/fail2ban?branch=master) / [![Coverage Status](https://coveralls.io/repos/fail2ban/fail2ban/badge.svg?branch=0.11)](https://coveralls.io/github/fail2ban/fail2ban?branch=0.11) (0.11 branch) / [![Coverage Status](https://coveralls.io/repos/fail2ban/fail2ban/badge.svg?branch=0.10)](https://coveralls.io/github/fail2ban/fail2ban?branch=0.10) / (0.10 branch)
* codecov.io: [![codecov.io](https://codecov.io/gh/fail2ban/fail2ban/coverage.svg?branch=0.11)](https://codecov.io/gh/fail2ban/fail2ban/branch/0.11) (0.11 branch) / [![codecov.io](https://codecov.io/gh/fail2ban/fail2ban/coverage.svg?branch=0.10)](https://codecov.io/gh/fail2ban/fail2ban/branch/0.10) (0.10 branch)
* codecov.io: [![codecov.io](https://codecov.io/gh/fail2ban/fail2ban/coverage.svg?branch=master)](https://codecov.io/gh/fail2ban/fail2ban/branch/master) / [![codecov.io](https://codecov.io/gh/fail2ban/fail2ban/coverage.svg?branch=0.11)](https://codecov.io/gh/fail2ban/fail2ban/branch/0.11) (0.11 branch) / [![codecov.io](https://codecov.io/gh/fail2ban/fail2ban/coverage.svg?branch=0.10)](https://codecov.io/gh/fail2ban/fail2ban/branch/0.10) (0.10 branch)
Contact:
--------
+49
View File
@@ -0,0 +1,49 @@
# Fail2Ban configuration file
#
# Author: Chris Caron <lead2gold@gmail.com>
#
#
[Definition]
# Option: actionstart
# Notes.: command executed once at the start of Fail2Ban.
# Values: CMD
#
actionstart = printf %%b "The jail <name> as been started successfully." | <apprise> -t "[Fail2Ban] <name>: started on `uname -n`"
# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD
#
actionstop = printf %%b "The jail <name> has been stopped." | <apprise> -t "[Fail2Ban] <name>: stopped on `uname -n`"
# 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: See jail.conf(5) man page
# Values: CMD
#
actionban = printf %%b "The IP <ip> has just been banned by Fail2Ban after <failures> attempts against <name>" | <apprise> -n "warning" -t "[Fail2Ban] <name>: banned <ip> from `uname -n`"
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: See jail.conf(5) man page
# Values: CMD
#
actionunban =
[Init]
# Define location of the default apprise configuration file to use
#
config = /etc/fail2ban/apprise.conf
#
apprise = apprise -c "<config>"
-19
View File
@@ -1,19 +0,0 @@
# Fail2ban reporting to badips.com
#
# Note: This reports an IP only and does not actually ban traffic. Use
# another action in the same jail if you want bans to occur.
#
# Set the category to the appropriate value before use.
#
# To get see register and optional key to get personalised graphs see:
# http://www.badips.com/blog/personalized-statistics-track-the-attackers-of-all-your-servers-with-one-key
[Definition]
actionban = curl --fail --user-agent "<agent>" http://www.badips.com/add/<category>/<ip>
[Init]
# Option: category
# Notes.: Values are from the list here: http://www.badips.com/get/categories
category =
-391
View File
@@ -1,391 +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.
import sys
if sys.version_info < (2, 7): # pragma: no cover
raise ImportError("badips.py action requires Python >= 2.7")
import json
import threading
import logging
if sys.version_info >= (3, ): # pragma: 2.x no cover
from urllib.request import Request, urlopen
from urllib.parse import urlencode
from urllib.error import HTTPError
else: # pragma: 3.x no cover
from urllib2 import Request, urlopen, HTTPError
from urllib import urlencode
from fail2ban.server.actions import Actions, ActionBase, BanTicket
from fail2ban.helpers import splitwords, str2LogLevel
class BadIPsAction(ActionBase): # pragma: no cover - may be unavailable
"""Fail2Ban action which reports bans to badips.com, and also
blacklist bad IPs listed on badips.com by using another action's
ban method.
Parameters
----------
jail : Jail
The jail which the action belongs to.
name : str
Name assigned to the action.
category : str
Valid badips.com category for reporting failures.
score : int, optional
Minimum score for bad IPs. Default 3.
age : str, optional
Age of last report for bad IPs, per badips.com syntax.
Default "24h" (24 hours)
banaction : str, optional
Name of banaction to use for blacklisting bad IPs. If `None`,
no blacklist of IPs will take place.
Default `None`.
bancategory : str, optional
Name of category to use for blacklisting, which can differ
from category used for reporting. e.g. may want to report
"postfix", but want to use whole "mail" category for blacklist.
Default `category`.
bankey : str, optional
Key issued by badips.com to retrieve personal list
of blacklist IPs.
updateperiod : int, optional
Time in seconds between updating bad IPs blacklist.
Default 900 (15 minutes)
loglevel : int/str, optional
Log level of the message when an IP is (un)banned.
Default `DEBUG`.
Can be also supplied as two-value list (comma- or space separated) to
provide level of the summary message when a group of IPs is (un)banned.
Example `DEBUG,INFO`.
agent : str, optional
User agent transmitted to server.
Default `Fail2Ban/ver.`
Raises
------
ValueError
If invalid `category`, `score`, `banaction` or `updateperiod`.
"""
TIMEOUT = 10
_badips = "https://www.badips.com"
def _Request(self, url, **argv):
return Request(url, headers={'User-Agent': self.agent}, **argv)
def __init__(self, jail, name, category, score=3, age="24h",
banaction=None, bancategory=None, bankey=None, updateperiod=900,
loglevel='DEBUG', agent="Fail2Ban", timeout=TIMEOUT):
super(BadIPsAction, self).__init__(jail, name)
self.timeout = timeout
self.agent = agent
self.category = category
self.score = score
self.age = age
self.banaction = banaction
self.bancategory = bancategory or category
self.bankey = bankey
loglevel = splitwords(loglevel)
self.sumloglevel = str2LogLevel(loglevel[-1])
self.loglevel = str2LogLevel(loglevel[0])
self.updateperiod = updateperiod
self._bannedips = set()
# Used later for threading.Timer for updating badips
self._timer = None
@staticmethod
def isAvailable(timeout=1):
try:
response = urlopen(Request("/".join([BadIPsAction._badips]),
headers={'User-Agent': "Fail2Ban"}), timeout=timeout)
return True, ''
except Exception as e: # pragma: no cover
return False, e
def logError(self, response, what=''): # pragma: no cover - sporadical (502: Bad Gateway, etc)
messages = {}
try:
messages = json.loads(response.read().decode('utf-8'))
except:
pass
self._logSys.error(
"%s. badips.com response: '%s'", what,
messages.get('err', 'Unknown'))
def getCategories(self, incParents=False):
"""Get badips.com categories.
Returns
-------
set
Set of categories.
Raises
------
HTTPError
Any issues with badips.com request.
ValueError
If badips.com response didn't contain necessary information
"""
try:
response = urlopen(
self._Request("/".join([self._badips, "get", "categories"])), timeout=self.timeout)
except HTTPError as response: # pragma: no cover
self.logError(response, "Failed to fetch categories")
raise
else:
response_json = json.loads(response.read().decode('utf-8'))
if not 'categories' in response_json:
err = "badips.com response lacked categories specification. Response was: %s" \
% (response_json,)
self._logSys.error(err)
raise ValueError(err)
categories = response_json['categories']
categories_names = set(
value['Name'] for value in categories)
if incParents:
categories_names.update(set(
value['Parent'] for value in categories
if "Parent" in value))
return categories_names
def getList(self, category, score, age, key=None):
"""Get badips.com list of bad IPs.
Parameters
----------
category : str
Valid badips.com category.
score : int
Minimum score for bad IPs.
age : str
Age of last report for bad IPs, per badips.com syntax.
key : str, optional
Key issued by badips.com to fetch IPs reported with the
associated key.
Returns
-------
set
Set of bad IPs.
Raises
------
HTTPError
Any issues with badips.com request.
"""
try:
url = "?".join([
"/".join([self._badips, "get", "list", category, str(score)]),
urlencode({'age': age})])
if key:
url = "&".join([url, urlencode({'key': key})])
self._logSys.debug('badips.com: get list, url: %r', url)
response = urlopen(self._Request(url), timeout=self.timeout)
except HTTPError as response: # pragma: no cover
self.logError(response, "Failed to fetch bad IP list")
raise
else:
return set(response.read().decode('utf-8').split())
@property
def category(self):
"""badips.com category for reporting IPs.
"""
return self._category
@category.setter
def category(self, category):
if category not in self.getCategories():
self._logSys.error("Category name '%s' not valid. "
"see badips.com for list of valid categories",
category)
raise ValueError("Invalid category: %s" % category)
self._category = category
@property
def bancategory(self):
"""badips.com bancategory for fetching IPs.
"""
return self._bancategory
@bancategory.setter
def bancategory(self, bancategory):
if bancategory != "any" and bancategory not in self.getCategories(incParents=True):
self._logSys.error("Category name '%s' not valid. "
"see badips.com for list of valid categories",
bancategory)
raise ValueError("Invalid bancategory: %s" % bancategory)
self._bancategory = bancategory
@property
def score(self):
"""badips.com minimum score for fetching IPs.
"""
return self._score
@score.setter
def score(self, score):
score = int(score)
if 0 <= score <= 5:
self._score = score
else:
raise ValueError("Score must be 0-5")
@property
def banaction(self):
"""Jail action to use for banning/unbanning.
"""
return self._banaction
@banaction.setter
def banaction(self, banaction):
if banaction is not None and banaction not in self._jail.actions:
self._logSys.error("Action name '%s' not in jail '%s'",
banaction, self._jail.name)
raise ValueError("Invalid banaction")
self._banaction = banaction
@property
def updateperiod(self):
"""Period in seconds between banned bad IPs will be updated.
"""
return self._updateperiod
@updateperiod.setter
def updateperiod(self, updateperiod):
updateperiod = int(updateperiod)
if updateperiod > 0:
self._updateperiod = updateperiod
else:
raise ValueError("Update period must be integer greater than 0")
def _banIPs(self, ips):
for ip in ips:
try:
ai = Actions.ActionInfo(BanTicket(ip), self._jail)
self._jail.actions[self.banaction].ban(ai)
except Exception as e:
self._logSys.error(
"Error banning IP %s for jail '%s' with action '%s': %s",
ip, self._jail.name, self.banaction, e,
exc_info=self._logSys.getEffectiveLevel()<=logging.DEBUG)
else:
self._bannedips.add(ip)
self._logSys.log(self.loglevel,
"Banned IP %s for jail '%s' with action '%s'",
ip, self._jail.name, self.banaction)
def _unbanIPs(self, ips):
for ip in ips:
try:
ai = Actions.ActionInfo(BanTicket(ip), self._jail)
self._jail.actions[self.banaction].unban(ai)
except Exception as e:
self._logSys.error(
"Error unbanning IP %s for jail '%s' with action '%s': %s",
ip, self._jail.name, self.banaction, e,
exc_info=self._logSys.getEffectiveLevel()<=logging.DEBUG)
else:
self._logSys.log(self.loglevel,
"Unbanned IP %s for jail '%s' with action '%s'",
ip, self._jail.name, self.banaction)
finally:
self._bannedips.remove(ip)
def start(self):
"""If `banaction` set, blacklists bad IPs.
"""
if self.banaction is not None:
self.update()
def update(self):
"""If `banaction` set, updates blacklisted IPs.
Queries badips.com for list of bad IPs, removing IPs from the
blacklist if no longer present, and adds new bad IPs to the
blacklist.
"""
if self.banaction is not None:
if self._timer:
self._timer.cancel()
self._timer = None
try:
ips = self.getList(
self.bancategory, self.score, self.age, self.bankey)
# Remove old IPs no longer listed
s = self._bannedips - ips
m = len(s)
self._unbanIPs(s)
# Add new IPs which are now listed
s = ips - self._bannedips
p = len(s)
self._banIPs(s)
if m != 0 or p != 0:
self._logSys.log(self.sumloglevel,
"Updated IPs for jail '%s' (-%d/+%d)",
self._jail.name, m, p)
self._logSys.debug(
"Next update for jail '%' in %i seconds",
self._jail.name, self.updateperiod)
finally:
self._timer = threading.Timer(self.updateperiod, self.update)
self._timer.start()
def stop(self):
"""If `banaction` set, clears blacklisted IPs.
"""
if self.banaction is not None:
if self._timer:
self._timer.cancel()
self._timer = None
self._unbanIPs(self._bannedips.copy())
def ban(self, aInfo):
"""Reports banned IP to badips.com.
Parameters
----------
aInfo : dict
Dictionary which includes information in relation to
the ban.
Raises
------
HTTPError
Any issues with badips.com request.
"""
try:
url = "/".join([self._badips, "add", self.category, str(aInfo['ip'])])
self._logSys.debug('badips.com: ban, url: %r', url)
response = urlopen(self._Request(url), timeout=self.timeout)
except HTTPError as response: # pragma: no cover
self.logError(response, "Failed to ban")
raise
else:
messages = json.loads(response.read().decode('utf-8'))
self._logSys.debug(
"Response from badips.com report: '%s'",
messages['suc'])
Action = BadIPsAction
+7 -2
View File
@@ -44,7 +44,7 @@ actioncheck =
#actionban = curl -s -o /dev/null https://www.cloudflare.com/api_json.html -d 'a=ban' -d 'tkn=<cftoken>' -d 'email=<cfuser>' -d 'key=<ip>'
# API v4
actionban = curl -s -o /dev/null -X POST <_cf_api_prms> \
-d '{"mode":"block","configuration":{"target":"ip","value":"<ip>"},"notes":"Fail2Ban <name>"}' \
-d '{"mode":"block","configuration":{"target":"<cftarget>","value":"<ip>"},"notes":"Fail2Ban <name>"}' \
<_cf_api_url>
# Option: actionunban
@@ -59,7 +59,7 @@ actionban = curl -s -o /dev/null -X POST <_cf_api_prms> \
#actionunban = curl -s -o /dev/null https://www.cloudflare.com/api_json.html -d 'a=nul' -d 'tkn=<cftoken>' -d 'email=<cfuser>' -d 'key=<ip>'
# API v4
actionunban = id=$(curl -s -X GET <_cf_api_prms> \
"<_cf_api_url>?mode=block&configuration_target=ip&configuration_value=<ip>&page=1&per_page=1&notes=Fail2Ban%%20<name>" \
"<_cf_api_url>?mode=block&configuration_target=<cftarget>&configuration_value=<ip>&page=1&per_page=1&notes=Fail2Ban%%20<name>" \
| { jq -r '.result[0].id' 2>/dev/null || tr -d '\n' | sed -nE 's/^.*"result"\s*:\s*\[\s*\{\s*"id"\s*:\s*"([^"]+)".*$/\1/p'; })
if [ -z "$id" ]; then echo "<name>: id for <ip> cannot be found"; exit 0; fi;
curl -s -o /dev/null -X DELETE <_cf_api_prms> "<_cf_api_url>/$id"
@@ -81,3 +81,8 @@ _cf_api_prms = -H 'X-Auth-Email: <cfuser>' -H 'X-Auth-Key: <cftoken>' -H 'Conten
cftoken =
cfuser =
cftarget = ip
[Init?family=inet6]
cftarget = ip6
+1 -1
View File
@@ -102,7 +102,7 @@ logpath = /dev/null
# Notes.: Your system mail command. Is passed 2 args: subject and recipient
# Values: CMD
#
mailcmd = mail -s
mailcmd = mail -E 'set escape' -s
# Option: mailargs
# Notes.: Additional arguments to mail command. e.g. for standard Unix mail:
+1 -1
View File
@@ -179,7 +179,7 @@ tcpflags =
# Notes.: Your system mail command. Is passed 2 args: subject and recipient
# Values: CMD
#
mailcmd = mail -s
mailcmd = mail -E 'set escape' -s
# Option: mailargs
# Notes.: Additional arguments to mail command. e.g. for standard Unix mail:
+39 -6
View File
@@ -18,21 +18,46 @@ before = firewallcmd-common.conf
[Definition]
actionstart = ipset create <ipmset> hash:ip timeout <default-ipsettime> <familyopt>
actionstart = <ipstype_<ipsettype>/actionstart>
firewall-cmd --direct --add-rule <family> filter <chain> 0 <actiontype> -m set --match-set <ipmset> src -j <blocktype>
actionflush = ipset flush <ipmset>
actionflush = <ipstype_<ipsettype>/actionflush>
actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 <actiontype> -m set --match-set <ipmset> src -j <blocktype>
<actionflush>
ipset destroy <ipmset>
<ipstype_<ipsettype>/actionstop>
actionban = ipset add <ipmset> <ip> timeout <ipsettime> -exist
actionban = <ipstype_<ipsettype>/actionban>
# actionprolong = %(actionban)s
actionunban = <ipstype_<ipsettype>/actionunban>
[ipstype_ipset]
actionstart = ipset create <ipmset> hash:ip timeout <default-ipsettime> <familyopt>
actionflush = ipset flush <ipmset>
actionstop = ipset destroy <ipmset>
actionban = ipset add <ipmset> <ip> timeout <ipsettime> -exist
actionunban = ipset del <ipmset> <ip> -exist
[ipstype_firewalld]
actionstart = firewall-cmd --direct --new-ipset=<ipmset> --type=hash:ip --option=timeout=<default-ipsettime> <firewalld_familyopt>
# TODO: there doesn't seem to be an explicit way to invoke the ipset flush function using firewall-cmd
actionflush =
actionstop = firewall-cmd --direct --delete-ipset=<ipmset>
actionban = firewall-cmd --ipset=<ipmset> --add-entry=<ip>
actionunban = firewall-cmd --ipset=<ipmset> --remove-entry=<ip>
[Init]
# Option: chain
@@ -56,6 +81,12 @@ ipsettime = 0
# banaction = %(known/banaction)s[ipsettime='<timeout-bantime>']
timeout-bantime = $([ "<bantime>" -le 2147483 ] && echo "<bantime>" || echo 0)
# Option: ipsettype
# Notes.: defines type of ipset used for match-set (firewalld or ipset)
# Values: firewalld or ipset
# Default: ipset
ipsettype = ipset
# Option: actiontype
# Notes.: defines additions to the blocking rule
# Values: leave empty to block all attempts from the host
@@ -71,18 +102,20 @@ allports = -p <protocol>
# Option: multiport
# Notes.: addition to block access only to specific ports
# Usage.: use in jail config: banaction = firewallcmd-ipset[actiontype=<multiport>]
multiport = -p <protocol> -m multiport --dports "$(echo '<port>' | sed s/:/-/g)"
multiport = -p <protocol> -m multiport --dports <port>
ipmset = f2b-<name>
familyopt =
firewalld_familyopt =
[Init?family=inet6]
ipmset = f2b-<name>6
familyopt = family inet6
firewalld_familyopt = --option=family=inet6
# DEV NOTES:
#
# Author: Edgar Hoch and Daniel Black
# Author: Edgar Hoch, Daniel Black, Sergey Brester and Mihail Politaev
# firewallcmd-new / iptables-ipset-proto6 combined for maximium goodness
+2 -2
View File
@@ -11,9 +11,9 @@ before = firewallcmd-common.conf
actionstart = firewall-cmd --direct --add-chain <family> filter f2b-<name>
firewall-cmd --direct --add-rule <family> filter f2b-<name> 1000 -j RETURN
firewall-cmd --direct --add-rule <family> filter <chain> 0 -m conntrack --ctstate NEW -p <protocol> -m multiport --dports "$(echo '<port>' | sed s/:/-/g)" -j f2b-<name>
firewall-cmd --direct --add-rule <family> filter <chain> 0 -m conntrack --ctstate NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 -m conntrack --ctstate NEW -p <protocol> -m multiport --dports "$(echo '<port>' | sed s/:/-/g)" -j f2b-<name>
actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 -m conntrack --ctstate NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
firewall-cmd --direct --remove-rules <family> filter f2b-<name>
firewall-cmd --direct --remove-chain <family> filter f2b-<name>
+2 -2
View File
@@ -10,9 +10,9 @@ before = firewallcmd-common.conf
actionstart = firewall-cmd --direct --add-chain <family> filter f2b-<name>
firewall-cmd --direct --add-rule <family> filter f2b-<name> 1000 -j RETURN
firewall-cmd --direct --add-rule <family> filter <chain> 0 -m state --state NEW -p <protocol> -m multiport --dports "$(echo '<port>' | sed s/:/-/g)" -j f2b-<name>
firewall-cmd --direct --add-rule <family> filter <chain> 0 -m state --state NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 -m state --state NEW -p <protocol> -m multiport --dports "$(echo '<port>' | sed s/:/-/g)" -j f2b-<name>
actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 -m state --state NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
firewall-cmd --direct --remove-rules <family> filter f2b-<name>
firewall-cmd --direct --remove-chain <family> filter f2b-<name>
+2 -2
View File
@@ -37,8 +37,8 @@ actioncheck =
fwcmd_rich_rule = rule family='<family>' source address='<ip>' port port='$p' protocol='<protocol>' %(rich-suffix)s
actionban = ports="$(echo '<port>' | sed s/:/-/g)"; for p in $(echo $ports | tr ", " " "); do firewall-cmd --add-rich-rule="%(fwcmd_rich_rule)s"; done
actionban = ports="<port>"; for p in $(echo $ports | tr ", " " "); do firewall-cmd --add-rich-rule="%(fwcmd_rich_rule)s"; done
actionunban = ports="$(echo '<port>' | sed s/:/-/g)"; for p in $(echo $ports | tr ", " " "); do firewall-cmd --remove-rich-rule="%(fwcmd_rich_rule)s"; done
actionunban = ports="<port>"; for p in $(echo $ports | tr ", " " "); do firewall-cmd --remove-rich-rule="%(fwcmd_rich_rule)s"; done
rich-suffix = <rich-blocktype>
+4 -4
View File
@@ -17,7 +17,7 @@ actionstart = printf %%b "Hi,\n
The jail <name> has been started successfully.\n
Output will be buffered until <lines> lines are available.\n
Regards,\n
Fail2Ban"|mail -s "[Fail2Ban] <name>: started on <fq-hostname>" <dest>
Fail2Ban"|mail -E 'set escape' -s "[Fail2Ban] <name>: started on <fq-hostname>" <dest>
# Option: actionstop
# Notes.: command executed at the stop of jail (or at the end of Fail2Ban)
@@ -28,13 +28,13 @@ actionstop = if [ -f <tmpfile> ]; then
These hosts have been banned by Fail2Ban.\n
`cat <tmpfile>`
Regards,\n
Fail2Ban"|mail -s "[Fail2Ban] <name>: Summary from <fq-hostname>" <dest>
Fail2Ban"|mail -E 'set escape' -s "[Fail2Ban] <name>: Summary from <fq-hostname>" <dest>
rm <tmpfile>
fi
printf %%b "Hi,\n
The jail <name> has been stopped.\n
Regards,\n
Fail2Ban"|mail -s "[Fail2Ban] <name>: stopped on <fq-hostname>" <dest>
Fail2Ban"|mail -E 'set escape' -s "[Fail2Ban] <name>: stopped on <fq-hostname>" <dest>
# Option: actioncheck
# Notes.: command executed once before each actionban command
@@ -55,7 +55,7 @@ actionban = printf %%b "`date`: <ip> (<failures> failures)\n" >> <tmpfile>
These hosts have been banned by Fail2Ban.\n
`cat <tmpfile>`
\nRegards,\n
Fail2Ban"|mail -s "[Fail2Ban] <name>: Summary" <dest>
Fail2Ban"|mail -E 'set escape' -s "[Fail2Ban] <name>: Summary" <dest>
rm <tmpfile>
fi
+1 -1
View File
@@ -72,7 +72,7 @@ actionunban =
# Notes.: Your system mail command. Is passed 2 args: subject and recipient
# Values: CMD
#
mailcmd = mail -s
mailcmd = mail -E 'set escape' -s
# Default name of the chain
#
+3 -3
View File
@@ -20,7 +20,7 @@ norestored = 1
actionstart = printf %%b "Hi,\n
The jail <name> has been started successfully.\n
Regards,\n
Fail2Ban"|mail -s "[Fail2Ban] <name>: started on <fq-hostname>" <dest>
Fail2Ban"|mail -E 'set escape' -s "[Fail2Ban] <name>: started on <fq-hostname>" <dest>
# Option: actionstop
# Notes.: command executed at the stop of jail (or at the end of Fail2Ban)
@@ -29,7 +29,7 @@ actionstart = printf %%b "Hi,\n
actionstop = printf %%b "Hi,\n
The jail <name> has been stopped.\n
Regards,\n
Fail2Ban"|mail -s "[Fail2Ban] <name>: stopped on <fq-hostname>" <dest>
Fail2Ban"|mail -E 'set escape' -s "[Fail2Ban] <name>: stopped on <fq-hostname>" <dest>
# Option: actioncheck
# Notes.: command executed once before each actionban command
@@ -49,7 +49,7 @@ actionban = printf %%b "Hi,\n
Here is more information about <ip> :\n
`%(_whois_command)s`\n
Regards,\n
Fail2Ban"|mail -s "[Fail2Ban] <name>: banned <ip> from <fq-hostname>" <dest>
Fail2Ban"|mail -E 'set escape' -s "[Fail2Ban] <name>: banned <ip> from <fq-hostname>" <dest>
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
+3 -3
View File
@@ -16,7 +16,7 @@ norestored = 1
actionstart = printf %%b "Hi,\n
The jail <name> has been started successfully.\n
Regards,\n
Fail2Ban"|mail -s "[Fail2Ban] <name>: started on <fq-hostname>" <dest>
Fail2Ban"|mail -E 'set escape' -s "[Fail2Ban] <name>: started on <fq-hostname>" <dest>
# Option: actionstop
# Notes.: command executed at the stop of jail (or at the end of Fail2Ban)
@@ -25,7 +25,7 @@ actionstart = printf %%b "Hi,\n
actionstop = printf %%b "Hi,\n
The jail <name> has been stopped.\n
Regards,\n
Fail2Ban"|mail -s "[Fail2Ban] <name>: stopped on <fq-hostname>" <dest>
Fail2Ban"|mail -E 'set escape' -s "[Fail2Ban] <name>: stopped on <fq-hostname>" <dest>
# Option: actioncheck
# Notes.: command executed once before each actionban command
@@ -43,7 +43,7 @@ actionban = printf %%b "Hi,\n
The IP <ip> has just been banned by Fail2Ban after
<failures> attempts against <name>.\n
Regards,\n
Fail2Ban"|mail -s "[Fail2Ban] <name>: banned <ip> from <fq-hostname>" <dest>
Fail2Ban"|mail -E 'set escape' -s "[Fail2Ban] <name>: banned <ip> from <fq-hostname>" <dest>
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
+9 -2
View File
@@ -84,8 +84,15 @@ srv_cfg_path = /etc/nginx/
#srv_cmd = nginx -c %(srv_cfg_path)s/nginx.conf
srv_cmd = nginx
# first test configuration is correct, hereafter send reload signal:
blck_lst_reload = %(srv_cmd)s -qt; if [ $? -eq 0 ]; then
# pid file (used to check nginx is running):
srv_pid = /run/nginx.pid
# command used to check whether nginx is running and configuration is valid:
srv_is_running = [ -f "%(srv_pid)s" ]
srv_check_cmd = %(srv_is_running)s && %(srv_cmd)s -qt
# first test nginx is running and configuration is correct, hereafter send reload signal:
blck_lst_reload = %(srv_check_cmd)s; if [ $? -eq 0 ]; then
%(srv_cmd)s -s reload; if [ $? -ne 0 ]; then echo 'reload failed.'; fi;
fi;
+40 -7
View File
@@ -13,16 +13,45 @@ actionstop =
actioncheck =
actionban = [ -n "<application>" ] && app="app <application>"
ufw insert <insertpos> <blocktype> from <ip> to <destination> $app
# ufw does "quickly process packets for which we already have a connection" in before.rules,
# therefore all related sockets should be closed
# actionban is using `ss` to do so, this only handles IPv4 and IPv6.
actionunban = [ -n "<application>" ] && app="app <application>"
ufw delete <blocktype> from <ip> to <destination> $app
actionban = if [ -n "<application>" ] && ufw app info "<application>"
then
ufw <add> <blocktype> from <ip> to <destination> app "<application>" comment "<comment>"
else
ufw <add> <blocktype> from <ip> to <destination> comment "<comment>"
fi
<kill>
actionunban = if [ -n "<application>" ] && ufw app info "<application>"
then
ufw delete <blocktype> from <ip> to <destination> app "<application>"
else
ufw delete <blocktype> from <ip> to <destination>
fi
# Option: kill-mode
# Notes.: can be set to ss or conntrack (may be extended later with other modes) to immediately drop all connections from banned IP, default empty (no kill)
# Example: banaction = ufw[kill-mode=ss]
kill-mode =
# intern conditional parameter used to provide killing mode after ban:
_kill_ =
_kill_ss = ss -K dst "[<ip>]"
_kill_conntrack = conntrack -D -s "<ip>"
# Option: kill
# Notes.: can be used to specify custom killing feature, by default depending on option kill-mode
# Examples: banaction = ufw[kill='ss -K "( sport = :http || sport = :https )" dst "[<ip>]"']
# banaction = ufw[kill='cutter "<ip>"']
kill = <_kill_<kill-mode>>
[Init]
# Option: insertpos
# Notes.: The position number in the firewall list to insert the block rule
insertpos = 1
# Option: add
# Notes.: can be set to "insert 1" to insert a rule at certain position (here 1):
add = prepend
# Option: blocktype
# Notes.: reject or deny
@@ -36,6 +65,10 @@ destination = any
# Notes.: application from sudo ufw app list
application =
# Option: comment
# Notes.: comment for rule added by fail2ban
comment = by Fail2Ban after <failures> attempts against <name>
# DEV NOTES:
#
# Author: Guilhem Lettron
+6
View File
@@ -55,6 +55,12 @@ socket = /var/run/fail2ban/fail2ban.sock
#
pidfile = /var/run/fail2ban/fail2ban.pid
# Option: allowipv6
# Notes.: Allows IPv6 interface:
# Default: auto
# Values: [ auto yes (on, true, 1) no (off, false, 0) ] Default: auto
#allowipv6 = auto
# Options: dbfile
# Notes.: Set the file for the fail2ban persistent data to be stored.
# A value of ":memory:" means database is only stored in memory
+2 -2
View File
@@ -2,11 +2,11 @@
[Definition]
failregex = ^<HOST> .*Googlebot.*$
failregex = ^\s*<HOST> \S+ \S+(?: \S+)?\s+\S+ "[A-Z]+ /\S* [^"]*" \d+ \d+ \"[^"]*\" "[^"]*\bGooglebot/[^"]*"
ignoreregex =
datepattern = ^[^\[]*\[({DATE})
datepattern = ^[^\[]*(\[{DATE}\s*\])
{^LN-BEG}
# DEV Notes:
+1 -1
View File
@@ -8,7 +8,7 @@ before = apache-common.conf
[Definition]
failregex = ^%(_apache_error_client)s (?:(?:AH0013[456]: )?Invalid (method|URI) in request\b|(?:AH00565: )?request failed: URI too long \(longer than \d+\)|request failed: erroneous characters after protocol string:|(?:AH00566: )?request failed: invalid characters in URI\b)
failregex = ^%(_apache_error_client)s (?:(?:AH001[23][456]: )?Invalid (method|URI) in request\b|(?:AH00565: )?request failed: URI too long \(longer than \d+\)|request failed: erroneous characters after protocol string:|(?:AH00566: )?request failed: invalid characters in URI\b)
ignoreregex =
+1 -1
View File
@@ -21,7 +21,7 @@ log_prefix= (?:NOTICE|SECURITY|WARNING)%(__pid_re)s:?(?:\[C-[\da-f]*\])?:? [^:]+
prefregex = ^%(__prefix_line)s%(log_prefix)s <F-CONTENT>.+</F-CONTENT>$
failregex = ^Registration from '[^']*' failed for '<HOST>(:\d+)?' - (?:Wrong password|Username/auth name mismatch|No matching peer found|Not a local domain|Device does not match ACL|Peer is not supposed to register|ACL error \(permit/deny\)|Not a local domain)$
^Call from '[^']*' \(<HOST>:\d+\) to extension '[^']*' rejected because extension not found in context
^Call from '[^']*' \((?:(?:TCP|UDP):)?<HOST>:\d+\) to extension '[^']*' rejected because extension not found in context
^(?:Host )?<HOST> (?:failed (?:to authenticate\b|MD5 authentication\b)|tried to authenticate with nonexistent user\b)
^No registration for peer '[^']*' \(from <HOST>\)$
^hacking attempt detected '<HOST>'$
+4 -3
View File
@@ -8,14 +8,15 @@ before = common.conf
[Definition]
_auth_worker = (?:dovecot: )?auth(?:-worker)?
_auth_worker_info = (?:conn \w+:auth(?:-worker)? \(uid=\w+\): auth(?:-worker)?<\d+>: )?
_daemon = (?:dovecot(?:-auth)?|auth)
prefregex = ^%(__prefix_line)s(?:%(_auth_worker)s(?:\([^\)]+\))?: )?(?:%(__pam_auth)s(?:\(dovecot:auth\))?: |(?:pop3|imap|managesieve|submission)-login: )?(?:Info: )?<F-CONTENT>.+</F-CONTENT>$
prefregex = ^%(__prefix_line)s(?:%(_auth_worker)s(?:\([^\)]+\))?: )?(?:%(__pam_auth)s(?:\(dovecot:auth\))?: |(?:pop3|imap|managesieve|submission)-login: )?(?:Info: )?%(_auth_worker_info)s<F-CONTENT>.+</F-CONTENT>$
failregex = ^authentication failure; logname=<F-ALT_USER1>\S*</F-ALT_USER1> uid=\S* euid=\S* tty=dovecot ruser=<F-USER>\S*</F-USER> rhost=<HOST>(?:\s+user=<F-ALT_USER>\S*</F-ALT_USER>)?\s*$
^(?:Aborted login|Disconnected|Remote closed connection|Client has quit the connection)(?::(?: [^ \(]+)+)? \((?:auth failed, \d+ attempts(?: in \d+ secs)?|tried to use (?:disabled|disallowed) \S+ auth|proxy dest auth failed)\):(?: user=<<F-USER>[^>]*</F-USER>>,)?(?: method=\S+,)? rip=<HOST>(?:[^>]*(?:, session=<\S+>)?)\s*$
^pam\(\S+,<HOST>(?:,\S*)?\): pam_authenticate\(\) failed: (?:User not known to the underlying authentication module: \d+ Time\(s\)|Authentication failure \(password mismatch\?\)|Permission denied)\s*$
^[a-z\-]{3,15}\(\S*,<HOST>(?:,\S*)?\): (?:unknown user|invalid credentials|Password mismatch)
^pam\(\S+,<HOST>(?:,\S*)?\): pam_authenticate\(\) failed: (?:User not known to the underlying authentication module: \d+ Time\(s\)|Authentication failure \([Pp]assword mismatch\?\)|Permission denied)\s*$
^[a-z\-]{3,15}\(\S*,<HOST>(?:,\S*)?\): (?:[Uu]nknown user|[Ii]nvalid credentials|[Pp]assword mismatch)
<mdre-<mode>>
mdre-aggressive = ^(?:Aborted login|Disconnected|Remote closed connection|Client has quit the connection)(?::(?: [^ \(]+)+)? \((?:no auth attempts|disconnected before auth was ready,|client didn't finish \S+ auth,)(?: (?:in|waited) \d+ secs)?\):(?: user=<[^>]*>,)?(?: method=\S+,)? rip=<HOST>(?:[^>]*(?:, session=<\S+>)?)\s*$
+1 -1
View File
@@ -14,7 +14,7 @@ before = common.conf
[Definition]
failregex = ^%(__prefix_line)s(https?:\/\/)([\da-z\.-]+)\.([a-z\.]{2,6})(\/[\w\.-]+)*\|\d{10}\|user\|<HOST>\|.+\|.+\|\d\|.*\|Login attempt failed for .+\.$
failregex = ^%(__prefix_line)s(?:https?:\/\/)[^|]+\|[^|]+\|[^|]+\|<ADDR>\|(?:[^|]*\|)*Login attempt failed (?:for|from) <F-USER>[^|]+</F-USER>\.$
ignoreregex =
+1 -1
View File
@@ -12,7 +12,7 @@ after = exim-common.local
host_info_pre = (?:H=([\w.-]+ )?(?:\(\S+\) )?)?
host_info_suf = (?::\d+)?(?: I=\[\S+\](:\d+)?)?(?: U=\S+)?(?: P=e?smtp)?(?: F=(?:<>|[^@]+@\S+))?\s
host_info = %(host_info_pre)s\[<HOST>\]%(host_info_suf)s
pid = (?: \[\d+\])?
pid = (?: \[\d+\]| \w+ exim\[\d+\]:)?
# DEV Notes:
# From exim source code: ./src/receive.c:add_host_info_for_log
@@ -6,24 +6,35 @@
#
import sys
from fail2ban.server.ipdns import DNSUtils, IPAddr
from threading import Thread
def process_args(argv):
if len(argv) != 2:
raise ValueError("Please provide a single IP as an argument. Got: %s\n"
% (argv[1:]))
if len(argv) - 1 not in (1, 2):
raise ValueError("Usage %s ip ?timeout?. Got: %s\n"
% (argv[0], argv[1:]))
ip = argv[1]
if not IPAddr(ip).isValid:
raise ValueError("Argument must be a single valid IP. Got: %s\n"
% ip)
return ip
return argv[1:]
google_ips = None
def is_googlebot(ip):
def is_googlebot(ip, timeout=55):
import re
host = DNSUtils.ipToName(ip)
timeout = float(timeout or 0)
if timeout:
def ipToNameTO(host, ip, timeout):
host[0] = DNSUtils.ipToName(ip)
host = [None]
th = Thread(target=ipToNameTO, args=(host, ip, timeout)); th.daemon=True; th.start()
th.join(timeout)
host = host[0]
else:
host = DNSUtils.ipToName(ip)
if not host or not re.match(r'.*\.google(bot)?\.com$', host):
return False
host_ips = DNSUtils.dnsToIp(host)
@@ -31,7 +42,7 @@ def is_googlebot(ip):
if __name__ == '__main__': # pragma: no cover
try:
ret = is_googlebot(process_args(sys.argv))
ret = is_googlebot(*process_args(sys.argv))
except ValueError as e:
sys.stderr.write(str(e))
sys.exit(2)
+1 -1
View File
@@ -3,7 +3,7 @@
[Definition]
failregex = ^: \((?:http|mod)_auth\.c\.\d+\) (?:password doesn\'t match .* username: .*|digest: auth failed for .*: wrong password|get_password failed), IP: <HOST>\s*$
failregex = ^\s*(?:: )?\(?(?:http|mod)_auth\.c\.\d+\) (?:password doesn\'t match for (?:\S+|.*?) username:\s+<F-USER>(?:\S+|.*?)</F-USER>\s*|digest: auth failed(?: for\s+<F-ALT_USER>(?:\S+|.*?)</F-ALT_USER>\s*)?: (?:wrong password|uri mismatch \([^\)]*\))|get_password failed),? IP: <HOST>\s*$
ignoreregex =
+25
View File
@@ -0,0 +1,25 @@
# Fail2Ban filter for Monitorix (HTTP built-in server)
#
[INCLUDES]
before = common.conf
[Definition]
_daemon = monitorix-httpd
# Option: failregex
# Notes.: regex to match the password failures messages in the logfile. The
# host must be matched by a group named "host". The tag "<HOST>" can
# be used for standard IP/hostname matching and is only an alias for
# (?:::f{4,6}:)?(?P<host>\S+)
# Values: TEXT
#
failregex = ^(?:\s+-)?\s*(?:NOTEXIST|AUTHERR|NOTALLOWED) - <ADDR>\b
# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.
# Values: TEXT
#
ignoreregex =
+15
View File
@@ -0,0 +1,15 @@
# Fail2Ban filter for failed MSSQL Server authentication attempts
[Definition]
failregex = ^\s*Logon\s+Login failed for user '<F-USER>(?:[^']*|.*)</F-USER>'\. [^'\[]+\[CLIENT: <ADDR>\]$
# DEV Notes:
# Tested with SQL Server 2019 on Ubuntu 18.04
#
# Example:
# 2020-02-24 14:48:55.12 Logon Login failed for user 'root'. Reason: Could not find a login matching the name provided. [CLIENT: 127.0.0.1]
#
# Author: Rüdiger Olschewsky
#
+1 -1
View File
@@ -32,7 +32,7 @@ __daemon_combs_re=(?:%(__pid_re)s?:\s+%(__daemon_re)s|%(__daemon_re)s%(__pid_re)
# hostname daemon_id spaces
# this can be optional (for instance if we match named native log files)
__line_prefix=(?:\s\S+ %(__daemon_combs_re)s\s+)?
__line_prefix=(?:\s*\S+ %(__daemon_combs_re)s\s+)?
prefregex = ^%(__line_prefix)s(?: error:)?\s*client(?: @\S*)? <HOST>#\S+(?: \([\S.]+\))?: <F-CONTENT>.+</F-CONTENT>\s(?:denied|\(NOTAUTH\))\s*$
+16
View File
@@ -0,0 +1,16 @@
# Fail2Ban filter to match bad requests to nginx
#
[Definition]
# The request often doesn't contain a method, only some encoded garbage
# This will also match requests that are entirely empty
failregex = ^<HOST> - \S+ \[\] "[^"]*" 400
datepattern = {^LN-BEG}%%ExY(?P<_sep>[-/.])%%m(?P=_sep)%%d[T ]%%H:%%M:%%S(?:[.,]%%f)?(?:\s*%%z)?
^[^\[]*\[({DATE})
{^LN-BEG}
journalmatch = _SYSTEMD_UNIT=nginx.service + _COMM=nginx
# Author: Jan Przybylak
+3 -1
View File
@@ -17,7 +17,9 @@ datepattern = {^LN-BEG}%%ExY(?P<_sep>[-/.])%%m(?P=_sep)%%d[T ]%%H:%%M:%%S(?:[.,]
^[^\[]*\[({DATE})
{^LN-BEG}
journalmatch = _SYSTEMD_UNIT=nginx.service + _COMM=nginx
# DEV Notes:
# Based on apache-botsearch filter
#
# Author: Frantisek Sumsal
# Author: Frantisek Sumsal
+18 -1
View File
@@ -3,15 +3,32 @@
[Definition]
mode = normal
failregex = ^ \[error\] \d+#\d+: \*\d+ user "(?:[^"]+|.*?)":? (?:password mismatch|was not found in "[^\"]*"), client: <HOST>, server: \S*, request: "\S+ \S+ HTTP/\d+\.\d+", host: "\S+"(?:, referrer: "\S+")?\s*$
mdre-auth = ^\s*\[error\] \d+#\d+: \*\d+ user "(?:[^"]+|.*?)":? (?:password mismatch|was not found in "[^\"]*"), client: <HOST>, server: \S*, request: "\S+ \S+ HTTP/\d+\.\d+", host: "\S+"(?:, referrer: "\S+")?\s*$
mdre-fallback = ^\s*\[crit\] \d+#\d+: \*\d+ SSL_do_handshake\(\) failed \(SSL: error:\S+(?: \S+){1,3} too (?:long|short)\)[^,]*, client: <HOST>
mdre-normal = %(mdre-auth)s
mdre-aggressive = %(mdre-auth)s
%(mdre-fallback)s
failregex = <mdre-<mode>>
ignoreregex =
datepattern = {^LN-BEG}
journalmatch = _SYSTEMD_UNIT=nginx.service + _COMM=nginx
# DEV NOTES:
# mdre-auth:
# Based on samples in https://github.com/fail2ban/fail2ban/pull/43/files
# Extensive search of all nginx auth failures not done yet.
#
# Author: Daniel Black
# mdre-fallback:
# Ban people checking for TLS_FALLBACK_SCSV repeatedly
# https://stackoverflow.com/questions/28010492/nginx-critical-error-with-ssl-handshaking/28010608#28010608
# Author: Stephan Orlowsky
+3
View File
@@ -44,3 +44,6 @@ failregex = ^\s*\[[a-z]+\] \d+#\d+: \*\d+ limiting requests, excess: [\d\.]+ by
ignoreregex =
datepattern = {^LN-BEG}
journalmatch = _SYSTEMD_UNIT=nginx.service + _COMM=nginx
+3 -3
View File
@@ -22,10 +22,10 @@ _daemon = nsd
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values: TEXT
failregex = ^%(__prefix_line)sinfo: ratelimit block .* query <HOST> TYPE255$
^%(__prefix_line)sinfo: .* <HOST> refused, no acl matches\.$
failregex = ^%(__prefix_line)sinfo: ratelimit block .* query <ADDR> TYPE255$
^%(__prefix_line)sinfo: .* from(?: client)? <ADDR> refused, no acl matches\.?$
ignoreregex =
datepattern = {^LN-BEG}Epoch
{^LN-BEG}
{^LN-BEG}
+10 -9
View File
@@ -12,16 +12,15 @@ before = common.conf
_daemon = postfix(-\w+)?/\w+(?:/smtp[ds])?
_port = (?::\d+)?
_pref = [A-Z]{4}
prefregex = ^%(__prefix_line)s<mdpr-<mode>> <F-CONTENT>.+</F-CONTENT>$
mdpr-normal = (?:\w+: reject:|(?:improper command pipelining|too many errors) after \S+)
mdre-normal=^RCPT from [^[]*\[<HOST>\]%(_port)s: 55[04] 5\.7\.1\s
^RCPT from [^[]*\[<HOST>\]%(_port)s: 45[04] 4\.7\.\d+ (?:Service unavailable\b|Client host rejected: cannot find your (reverse )?hostname\b)
^RCPT from [^[]*\[<HOST>\]%(_port)s: 450 4\.7\.\d+ (<[^>]*>)?: Helo command rejected: Host not found\b
^EHLO from [^[]*\[<HOST>\]%(_port)s: 504 5\.5\.\d+ (<[^>]*>)?: Helo command rejected: need fully-qualified hostname\b
^(RCPT|VRFY) from [^[]*\[<HOST>\]%(_port)s: 550 5\.1\.1\s
^RCPT from [^[]*\[<HOST>\]%(_port)s: 450 4\.1\.\d+ (<[^>]*>)?: Sender address rejected: Domain not found\b
# Extended RE for normal mode to match reject by unknown users or undeliverable address, can be set to empty to avoid this:
exre-user = |[Uu](?:ser unknown|ndeliverable address)
mdpr-normal = (?:\w+: (?:milter-)?reject:|(?:improper command pipelining|too many errors) after \S+)
mdre-normal=^%(_pref)s from [^[]*\[<HOST>\]%(_port)s: [45][50][04] [45]\.\d\.\d+ (?:(?:<[^>]*>)?: )?(?:(?:Helo command|(?:Sender|Recipient) address) rejected: )?(?:Service unavailable|(?:Client host|Command|Data command) rejected|Relay access denied|(?:Host|Domain) not found|need fully-qualified hostname|match%(exre-user)s)\b
^from [^[]*\[<HOST>\]%(_port)s:?
mdpr-auth = warning:
@@ -31,13 +30,15 @@ mdre-auth2= ^[^[]*\[<HOST>\]%(_port)s: SASL ((?i)LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5
# Mode "rbl" currently included in mode "normal", but if needed for jail "postfix-rbl" only:
mdpr-rbl = %(mdpr-normal)s
mdre-rbl = ^RCPT from [^[]*\[<HOST>\]%(_port)s: [45]54 [45]\.7\.1 Service unavailable; Client host \[\S+\] blocked\b
mdre-rbl = ^%(_pref)s from [^[]*\[<HOST>\]%(_port)s: [45]54 [45]\.7\.1 Service unavailable; Client host \[\S+\] blocked\b
# Mode "rbl" currently included in mode "normal" (within 1st rule)
mdpr-more = %(mdpr-normal)s
mdre-more = %(mdre-normal)s
mdpr-ddos = (?:lost connection after(?! DATA) [A-Z]+|disconnect(?= from \S+(?: \S+=\d+)* auth=0/(?:[1-9]|\d\d+)))
# Includes some of the log messages described in
# <http://www.postfix.org/POSTSCREEN_README.html>.
mdpr-ddos = (?:lost connection after(?! DATA) [A-Z]+|disconnect(?= from \S+(?: \S+=\d+)* auth=0/(?:[1-9]|\d\d+))|(?:PREGREET \d+|HANGUP) after \S+|COMMAND (?:TIME|COUNT|LENGTH) LIMIT)
mdre-ddos = ^from [^[]*\[<HOST>\]%(_port)s:?
mdpr-extra = (?:%(mdpr-auth)s|%(mdpr-normal)s)
+17
View File
@@ -0,0 +1,17 @@
# Fail2Ban filter for port scans detected by scanlogd
[INCLUDES]
# Read common prefixes. If any customizations available -- read them from
# common.local
before = common.conf
[Definition]
_daemon = scanlogd
failregex = ^%(__prefix_line)s<ADDR>(?::<F-PORT/>)? to \S+ ports\b
ignoreregex =
# Author: Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
+1 -1
View File
@@ -15,7 +15,7 @@ addr = (?:IPv6:<IP6>|<IP4>)
prefregex = ^<F-MLFID>%(__prefix_line)s</F-MLFID><F-CONTENT>.+</F-CONTENT>$
failregex = ^(\S+ )?\[%(addr)s\]( \(may be forged\))?: possible SMTP attack: command=AUTH, count=\d+$
^AUTH failure \(LOGIN\):(?: [^:]+:)? authentication failure: checkpass failed, user=<F-USER>(?:\S+|.*?)</F-USER>, relay=(?:\S+ )?\[%(addr)s\](?: \(may be forged\))?$
^AUTH failure \([^\)]+\):(?: [^:]+:)? (?:authentication failure|user not found): [^,]*, user=<F-USER>(?:\S+|.*?)</F-USER>, relay=(?:\S+ )?\[%(addr)s\](?: \(may be forged\))?$
ignoreregex =
journalmatch = _SYSTEMD_UNIT=sendmail.service
+3 -3
View File
@@ -21,12 +21,12 @@ before = common.conf
_daemon = (?:(sm-(mta|acceptingconnections)|sendmail))
__prefix_line = %(known/__prefix_line)s(?:\w{14,20}: )?
addr = (?:IPv6:<IP6>|<IP4>)
addr = (?:(?:IPv6:)?<IP6>|<IP4>)
prefregex = ^<F-MLFID>%(__prefix_line)s</F-MLFID><F-CONTENT>.+</F-CONTENT>$
cmnfailre = ^ruleset=check_rcpt, arg1=(?P<email><\S+@\S+>), relay=(\S+ )?\[%(addr)s\](?: \(may be forged\))?, reject=(550 5\.7\.1 (?P=email)\.\.\. Relaying denied\. (IP name possibly forged \[(\d+\.){3}\d+\]|Proper authentication required\.|IP name lookup failed \[(\d+\.){3}\d+\])|553 5\.1\.8 (?P=email)\.\.\. Domain of sender address \S+ does not exist|550 5\.[71]\.1 (?P=email)\.\.\. (Rejected: .*|User unknown))$
^ruleset=check_relay, arg1=(?P<dom>\S+), arg2=%(addr)s, relay=((?P=dom) )?\[(\d+\.){3}\d+\](?: \(may be forged\))?, reject=421 4\.3\.2 (Connection rate limit exceeded\.|Too many open connections\.)$
cmnfailre = ^ruleset=check_rcpt, arg1=(?P<email><\S+@\S+>), relay=(\S+ )?\[%(addr)s\](?: \(may be forged\))?, reject=(?:550 5\.7\.1(?: (?P=email)\.\.\.)?(?: Relaying denied\.)? (?:IP name possibly forged \[(\d+\.){3}\d+\]|Proper authentication required\.|IP name lookup failed \[(\d+\.){3}\d+\]|Fix reverse DNS for \S+)|553 5\.1\.8(?: (?P=email)\.\.\.)? Domain of sender address \S+ does not exist|550 5\.[71]\.1 (?P=email)\.\.\. (Rejected: .*|User unknown))$
^ruleset=check_relay(?:, arg\d+=\S*)*, relay=(\S+ )?\[%(addr)s\](?: \(may be forged\))?, reject=421 4\.3\.2 (Connection rate limit exceeded\.|Too many open connections\.)$
^rejecting commands from (\S* )?\[%(addr)s\] due to pre-greeting traffic after \d+ seconds$
^(?:\S+ )?\[%(addr)s\]: (?:(?i)expn|vrfy) \S+ \[rejected\]$
^<[^@]+@[^>]+>\.\.\. No such user here$
+11 -5
View File
@@ -5,17 +5,23 @@ before = apache-common.conf
[Definition]
# pattern: [Wed Apr 27 23:12:07.736196 2016] [:error] [pid 2460] [client 10.1.1.1:47296] WAR [Login denied for user "test"], referer: https://zoneminderurl/index.php
#
# patterns: [Mon Mar 28 16:50:49.522240 2016] [:error] [pid 1795] [client 10.1.1.1:50700] WAR [Login denied for user "username1"], referer: https://zoneminder/
# [Sun Mar 28 16:53:00.472693 2021] [php7:notice] [pid 11328] [client 10.1.1.1:39568] ERR [Could not retrieve user test details], referer: https://zm/
# [Sun Mar 28 16:59:14.150625 2021] [php7:notice] [pid 11336] [client 10.1.1.1:39654] ERR [Login denied for user "john"], referer: https://zm/
#
# Option: failregex
# Notes.: regex to match the password failure messages in the logfile.
# Notes.: regex to match the login failure and non-existent user error messages in the logfile.
failregex = ^%(_apache_error_client)s WAR \[Login denied for user "[^"]*"\]
prefregex = ^%(_apache_error_client)s (?:ERR|WAR) <F-CONTENT>\[(?:Login denied|Could not retrieve).*</F-CONTENT>$
failregex = ^\[Login denied for user "<F-USER>[^"]*</F-USER>"\]
^\[Could not retrieve user <F-USER>\S*</F-USER>
ignoreregex =
# Notes:
# Tested on Zoneminder 1.29.0
# Tested on Zoneminder 1.29 and 1.35.21
#
# Zoneminder versions > 1.3x use "ERR" and < 1.3x use "WAR" level logs, so i've kept both for compatibility reasons
#
# Author: John Marzella
+34 -18
View File
@@ -67,7 +67,7 @@ before = paths-debian.conf
# more aggressive example of formula has the same values only for factor "2.0 / 2.885385" :
#bantime.formula = ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)
# "bantime.multipliers" used to calculate next value of ban time instead of formula, coresponding
# "bantime.multipliers" used to calculate next value of ban time instead of formula, corresponding
# previously ban count and given "bantime.factor" (for multipliers default is 1);
# following example grows ban time by 1, 2, 4, 8, 16 ... and if last ban count greater as multipliers count,
# always used last multiplier (64 in example), for factor '1' and original ban time 600 - 10.6 hours
@@ -77,7 +77,7 @@ before = paths-debian.conf
#bantime.multipliers = 1 5 30 60 300 720 1440 2880
# "bantime.overalljails" (if true) specifies the search of IP in the database will be executed
# cross over all jails, if false (dafault), only current jail of the ban IP will be searched
# cross over all jails, if false (default), only current jail of the ban IP will be searched
#bantime.overalljails = false
# --------------------
@@ -227,6 +227,15 @@ action_mwl = %(action_)s
action_xarf = %(action_)s
xarf-login-attack[service=%(__name__)s, sender="%(sender)s", logpath="%(logpath)s", port="%(port)s"]
# ban & send a notification to one or more of the 50+ services supported by Apprise.
# See https://github.com/caronc/apprise/wiki for details on what is supported.
#
# You may optionally over-ride the default configuration line (containing the Apprise URLs)
# by using 'apprise[config="/alternate/path/to/apprise.cfg"]' otherwise
# /etc/fail2ban/apprise.conf is sourced for your supported notification configuration.
# action = %(action_)s
# apprise
# ban IP on CloudFlare & send an e-mail with whois report and relevant log lines
# to the destemail.
action_cf_mwl = cloudflare[cfuser="%(cfemail)s", cftoken="%(cfapikey)s"]
@@ -242,20 +251,6 @@ action_cf_mwl = cloudflare[cfuser="%(cfemail)s", cftoken="%(cfapikey)s"]
#
action_blocklist_de = blocklist_de[email="%(sender)s", service="%(__name__)s", apikey="%(blocklist_de_apikey)s", agent="%(fail2ban_agent)s"]
# Report ban via badips.com, and use as blacklist
#
# See BadIPsAction docstring in config/action.d/badips.py for
# documentation for this action.
#
# NOTE: This action relies on banaction being present on start and therefore
# should be last action defined for a jail.
#
action_badips = badips.py[category="%(__name__)s", banaction="%(banaction)s", agent="%(fail2ban_agent)s"]
#
# Report ban via badips.com (uses action.d/badips.conf for reporting only)
#
action_badips_report = badips[category="%(__name__)s", agent="%(fail2ban_agent)s"]
# Report ban via abuseipdb.com.
#
# See action.d/abuseipdb.conf for usage example and details.
@@ -375,8 +370,11 @@ banaction = %(banaction_allports)s
logpath = /opt/openhab/logs/request.log
# To use more aggressive http-auth modes set filter parameter "mode" in jail.local:
# normal (default), aggressive (combines all), auth or fallback
# See "tests/files/logs/nginx-http-auth" or "filter.d/nginx-http-auth.conf" for usage example and details.
[nginx-http-auth]
# mode = normal
port = http,https
logpath = %(nginx_error_log)s
@@ -392,8 +390,10 @@ logpath = %(nginx_error_log)s
port = http,https
logpath = %(nginx_error_log)s
maxretry = 2
[nginx-bad-request]
port = http,https
logpath = %(nginx_access_log)s
# Ban attackers that try to use PHP's URL-fopen() functionality
# through GET/POST variables. - Experimental, with more than a year
@@ -797,6 +797,14 @@ logpath = %(mysql_log)s
backend = %(mysql_backend)s
[mssql-auth]
# Default configuration for Microsoft SQL Server for Linux
# See the 'mssql-conf' manpage how to change logpath or port
logpath = /var/opt/mssql/log/errorlog
port = 1433
filter = mssql-auth
# Log wrong MongoDB auth (for details see filter 'filter.d/mongodb-auth.conf')
[mongodb-auth]
# change port when running with "--shardsvr" or "--configsvr" runtime operation
@@ -962,3 +970,11 @@ logpath = %(apache_error_log)s
# see `filter.d/traefik-auth.conf` for details and service example.
port = http,https
logpath = /var/log/traefik/access.log
[scanlogd]
logpath = %(syslog_local0)s
banaction = %(banaction_allports)s
[monitorix]
port = 8080
logpath = /var/log/monitorix-httpd
+2
View File
@@ -26,3 +26,5 @@ exim_main_log = /var/log/exim4/mainlog
# was in debian squeezy but not in wheezy
# /etc/proftpd/proftpd.conf (SystemLog)
proftpd_log = /var/log/proftpd/proftpd.log
roundcube_errors_log = /var/log/roundcube/errors.log
+1 -1
View File
@@ -230,7 +230,7 @@ class Fail2banClient(Fail2banCmdLine, Thread):
logSys.log(5, ' client phase %s', phase)
if not stream:
return False
# wait a litle bit for phase "start-ready" before enter active waiting:
# wait a little bit for phase "start-ready" before enter active waiting:
if phase is not None:
Utils.wait_for(lambda: phase.get('start-ready', None) is not None, 0.5, 0.001)
phase['configure'] = (True if stream else False)
+1 -1
View File
@@ -192,7 +192,7 @@ class Fail2banCmdLine():
cmdOpts = 'hc:s:p:xfbdtviqV'
cmdLongOpts = ['loglevel=', 'logtarget=', 'syslogsocket=', 'test', 'async',
'conf=', 'pidfile=', 'pname=', 'socket=',
'timeout=', 'str2sec=', 'help', 'version', 'dp', '--dump-pretty']
'timeout=', 'str2sec=', 'help', 'version', 'dp', 'dump-pretty']
optList, self._args = getopt.getopt(self._argv[1:], cmdOpts, cmdLongOpts)
except getopt.GetoptError:
self.dispUsage()
+2
View File
@@ -53,6 +53,7 @@ class Fail2banReader(ConfigReader):
opts = [["string", "loglevel", "INFO" ],
["string", "logtarget", "STDERR"],
["string", "syslogsocket", "auto"],
["string", "allowipv6", "auto"],
["string", "dbfile", "/var/lib/fail2ban/fail2ban.sqlite3"],
["int", "dbmaxmatches", None],
["string", "dbpurgeage", "1d"]]
@@ -74,6 +75,7 @@ class Fail2banReader(ConfigReader):
# Also dbfile should be set before all other database options.
# So adding order indices into items, to be stripped after sorting, upon return
order = {"thread":0, "syslogsocket":11, "loglevel":12, "logtarget":13,
"allowipv6": 14,
"dbfile":50, "dbmaxmatches":51, "dbpurgeage":51}
stream = list()
for opt in self.__opts:
+33 -29
View File
@@ -35,11 +35,11 @@ __license__ = "GPL"
import getopt
import logging
import re
import os
import shlex
import sys
import time
import time
import urllib
from optparse import OptionParser, Option
@@ -52,7 +52,7 @@ except ImportError:
from ..version import version, normVersion
from .filterreader import FilterReader
from ..server.filter import Filter, FileContainer
from ..server.filter import Filter, FileContainer, MyTime
from ..server.failregex import Regex, RegexException
from ..helpers import str2LogLevel, getVerbosityFormat, FormatterWithTraceBack, getLogger, \
@@ -269,15 +269,19 @@ class Fail2banRegex(object):
self.setJournalMatch(shlex.split(opts.journalmatch))
if opts.timezone:
self._filter.setLogTimeZone(opts.timezone)
self._filter.checkFindTime = False
if True: # not opts.out:
MyTime.setAlternateNow(0); # accept every date (years from 19xx up to end of current century, '%ExY' and 'Exy' patterns)
from ..server.strptime import _updateTimeRE
_updateTimeRE()
if opts.datepattern:
self.setDatePattern(opts.datepattern)
if opts.usedns:
self._filter.setUseDns(opts.usedns)
self._filter.returnRawHost = opts.raw
self._filter.checkFindTime = False
self._filter.checkAllRegex = opts.checkAllRegex and not opts.out
# ignore pending (without ID/IP), added to matches if it hits later (if ID/IP can be retreved)
self._filter.ignorePending = opts.out
self._filter.ignorePending = bool(opts.out)
# callback to increment ignored RE's by index (during process):
self._filter.onIgnoreRegex = self._onIgnoreRegex
self._backend = 'auto'
@@ -285,9 +289,6 @@ class Fail2banRegex(object):
def output(self, line):
if not self._opts.out: output(line)
def decode_line(self, line):
return FileContainer.decode_line('<LOG>', self._encoding, line)
def encode_line(self, line):
return line.encode(self._encoding, 'ignore')
@@ -326,26 +327,33 @@ class Fail2banRegex(object):
regex = regextype + 'regex'
# try to check - we've case filter?[options...]?:
basedir = self._opts.config
fltName = value
fltFile = None
fltOpt = {}
if regextype == 'fail':
fltName, fltOpt = extractOptions(value)
if fltName is not None:
if "." in fltName[~5:]:
tryNames = (fltName,)
else:
tryNames = (fltName, fltName + '.conf', fltName + '.local')
for fltFile in tryNames:
if not "/" in fltFile:
if os.path.basename(basedir) == 'filter.d':
fltFile = os.path.join(basedir, fltFile)
else:
fltFile = os.path.join(basedir, 'filter.d', fltFile)
if re.search(r'^/{0,3}[\w/_\-.]+(?:\[.*\])?$', value):
try:
fltName, fltOpt = extractOptions(value)
if "." in fltName[~5:]:
tryNames = (fltName,)
else:
basedir = os.path.dirname(fltFile)
if os.path.isfile(fltFile):
break
fltFile = None
tryNames = (fltName, fltName + '.conf', fltName + '.local')
for fltFile in tryNames:
if not "/" in fltFile:
if os.path.basename(basedir) == 'filter.d':
fltFile = os.path.join(basedir, fltFile)
else:
fltFile = os.path.join(basedir, 'filter.d', fltFile)
else:
basedir = os.path.dirname(fltFile)
if os.path.isfile(fltFile):
break
fltFile = None
except Exception as e:
output("ERROR: Wrong filter name or options: %s" % (str(e),))
output(" while parsing: %s" % (value,))
if self._verbose: raise(e)
return False
# if it is filter file:
if fltFile is not None:
if (basedir == self._opts.config
@@ -712,10 +720,6 @@ class Fail2banRegex(object):
return True
def file_lines_gen(self, hdlr):
for line in hdlr:
yield self.decode_line(line)
def start(self, args):
cmd_log, cmd_regex = args[:2]
@@ -734,10 +738,10 @@ class Fail2banRegex(object):
if os.path.isfile(cmd_log):
try:
hdlr = open(cmd_log, 'rb')
test_lines = FileContainer(cmd_log, self._encoding, doOpen=True)
self.output( "Use log file : %s" % cmd_log )
self.output( "Use encoding : %s" % self._encoding )
test_lines = self.file_lines_gen(hdlr)
except IOError as e: # pragma: no cover
output( e )
return False
+8 -7
View File
@@ -140,9 +140,10 @@ class JailReader(ConfigReader):
# Read filter
flt = self.__opts["filter"]
if flt:
filterName, filterOpt = extractOptions(flt)
if not filterName:
raise JailDefError("Invalid filter definition %r" % flt)
try:
filterName, filterOpt = extractOptions(flt)
except ValueError as e:
raise JailDefError("Invalid filter definition %r: %s" % (flt, e))
self.__filter = FilterReader(
filterName, self.__name, filterOpt,
share_config=self.share_config, basedir=self.getBaseDir())
@@ -174,10 +175,10 @@ class JailReader(ConfigReader):
if not act: # skip empty actions
continue
# join with previous line if needed (consider possible new-line):
actName, actOpt = extractOptions(act)
prevln = ''
if not actName:
raise JailDefError("Invalid action definition %r" % act)
try:
actName, actOpt = extractOptions(act)
except ValueError as e:
raise JailDefError("Invalid action definition %r: %s" % (act, e))
if actName.endswith(".py"):
self.__actions.append([
"set",
+9 -3
View File
@@ -371,7 +371,7 @@ OPTION_CRE = re.compile(r"^([^\[]+)(?:\[(.*)\])?\s*$", re.DOTALL)
# since v0.10 separator extended with `]\s*[` for support of multiple option groups, syntax
# `action = act[p1=...][p2=...]`
OPTION_EXTRACT_CRE = re.compile(
r'([\w\-_\.]+)=(?:"([^"]*)"|\'([^\']*)\'|([^,\]]*))(?:,|\]\s*\[|$)', re.DOTALL)
r'\s*([\w\-_\.]+)=(?:"([^"]*)"|\'([^\']*)\'|([^,\]]*))(?:,|\]\s*\[|$|(?P<wrngA>.+))|,?\s*$|(?P<wrngB>.+)', re.DOTALL)
# split by new-line considering possible new-lines within options [...]:
OPTION_SPLIT_CRE = re.compile(
r'(?:[^\[\s]+(?:\s*\[\s*(?:[\w\-_\.]+=(?:"[^"]*"|\'[^\']*\'|[^,\]]*)\s*(?:,|\]\s*\[)?\s*)*\])?\s*|\S+)(?=\n\s*|\s+|$)', re.DOTALL)
@@ -379,13 +379,19 @@ OPTION_SPLIT_CRE = re.compile(
def extractOptions(option):
match = OPTION_CRE.match(option)
if not match:
# TODO proper error handling
return None, None
raise ValueError("unexpected option syntax")
option_name, optstr = match.groups()
option_opts = dict()
if optstr:
for optmatch in OPTION_EXTRACT_CRE.finditer(optstr):
if optmatch.group("wrngA"):
raise ValueError("unexpected syntax at %d after option %r: %s" % (
optmatch.start("wrngA"), optmatch.group(1), optmatch.group("wrngA")[0:25]))
if optmatch.group("wrngB"):
raise ValueError("expected option, wrong syntax at %d: %s" % (
optmatch.start("wrngB"), optmatch.group("wrngB")[0:25]))
opt = optmatch.group(1)
if not opt: continue
value = [
val for val in optmatch.group(2,3,4) if val is not None][0]
option_opts[opt.strip()] = value.strip()
+1 -1
View File
@@ -134,7 +134,7 @@ protocol = [
["get <JAIL> ignoreregex", "gets the list of regular expressions which matches patterns to ignore for <JAIL>"],
["get <JAIL> findtime", "gets the time for which the filter will look back for failures for <JAIL>"],
["get <JAIL> bantime", "gets the time a host is banned for <JAIL>"],
["get <JAIL> datepattern", "gets the patern used to match date/times for <JAIL>"],
["get <JAIL> datepattern", "gets the pattern used to match date/times for <JAIL>"],
["get <JAIL> usedns", "gets the usedns setting for <JAIL>"],
["get <JAIL> banip [<SEP>|--with-time]", "gets the list of of banned IP addresses for <JAIL>. Optionally the separator character ('<SEP>', default is space) or the option '--with-time' (printing the times of ban) may be specified. The IPs are ordered by end of ban."],
["get <JAIL> maxretry", "gets the number of failures allowed for <JAIL>"],
+33 -23
View File
@@ -30,7 +30,10 @@ import tempfile
import threading
import time
from abc import ABCMeta
from collections import MutableMapping
try:
from collections.abc import MutableMapping
except ImportError:
from collections import MutableMapping
from .failregex import mapTag2Opt
from .ipdns import DNSUtils
@@ -407,7 +410,7 @@ class CommandAction(ActionBase):
cmd = self.replaceTag(tag, self._properties,
conditional=('family='+family if family else ''),
cache=self.__substCache)
if '<' not in cmd or not family: return cmd
if not family or '<' not in cmd: return cmd
# replace family as dynamic tags, important - don't cache, no recursion and auto-escape here:
cmd = self.replaceDynamicTags(cmd, {'family':family})
return cmd
@@ -974,31 +977,38 @@ class CommandAction(ActionBase):
except (KeyError, TypeError):
family = ''
# invariant check:
if self.actioncheck:
# don't repair/restore if unban (no matter):
def _beforeRepair():
if cmd == '<actionunban>' and not self._properties.get('actionrepair_on_unban'):
self._logSys.error("Invariant check failed. Unban is impossible.")
repcnt = 0
while True:
# got some error, do invariant check:
if repcnt and self.actioncheck:
# don't repair/restore if unban (no matter):
def _beforeRepair():
if cmd == '<actionunban>' and not self._properties.get('actionrepair_on_unban'):
self._logSys.error("Invariant check failed. Unban is impossible.")
return False
return True
# check and repair if broken:
ret = self._invariantCheck(family, _beforeRepair, forceStart=(cmd != '<actionunban>'))
# if not sane (and not restored) return:
if ret != 1:
return False
return True
# check and repair if broken:
ret = self._invariantCheck(family, _beforeRepair, forceStart=(cmd != '<actionunban>'))
# if not sane (and not restored) return:
if ret != 1:
return False
# Replace static fields
realCmd = self.replaceTag(cmd, self._properties,
conditional=('family='+family if family else ''), cache=self.__substCache)
# Replace static fields
realCmd = self.replaceTag(cmd, self._properties,
conditional=('family='+family if family else ''), cache=self.__substCache)
# Replace dynamical tags, important - don't cache, no recursion and auto-escape here
if aInfo is not None:
realCmd = self.replaceDynamicTags(realCmd, aInfo)
else:
realCmd = cmd
# Replace dynamical tags, important - don't cache, no recursion and auto-escape here
if aInfo is not None:
realCmd = self.replaceDynamicTags(realCmd, aInfo)
else:
realCmd = cmd
return self.executeCmd(realCmd, self.timeout)
# try execute command:
ret = self.executeCmd(realCmd, self.timeout)
repcnt += 1
if ret or repcnt > 1:
return ret
@staticmethod
def executeCmd(realCmd, timeout=60, **kwargs):
+34 -26
View File
@@ -28,7 +28,10 @@ import logging
import os
import sys
import time
from collections import Mapping
try:
from collections.abc import Mapping
except ImportError:
from collections import Mapping
try:
from collections import OrderedDict
except ImportError:
@@ -81,7 +84,7 @@ class Actions(JailThread, Mapping):
self._jail = jail
self._actions = OrderedDict()
## The ban manager.
self.__banManager = BanManager()
self.banManager = BanManager()
self.banEpoch = 0
self.__lastConsistencyCheckTM = 0
## Precedence of ban (over unban), so max number of tickets banned (to call an unban check):
@@ -200,7 +203,7 @@ class Actions(JailThread, Mapping):
def setBanTime(self, value):
value = MyTime.str2seconds(value)
self.__banManager.setBanTime(value)
self.banManager.setBanTime(value)
logSys.info(" banTime: %s" % value)
##
@@ -209,10 +212,10 @@ class Actions(JailThread, Mapping):
# @return the time
def getBanTime(self):
return self.__banManager.getBanTime()
return self.banManager.getBanTime()
def getBanned(self, ids):
lst = self.__banManager.getBanList()
lst = self.banManager.getBanList()
if not ids:
return lst
if len(ids) == 1:
@@ -227,7 +230,7 @@ class Actions(JailThread, Mapping):
list
The list of banned IP addresses.
"""
return self.__banManager.getBanList(ordered=True, withTime=withTime)
return self.banManager.getBanList(ordered=True, withTime=withTime)
def addBannedIP(self, ip):
"""Ban an IP or list of IPs."""
@@ -279,7 +282,7 @@ class Actions(JailThread, Mapping):
if db and self._jail.database is not None:
self._jail.database.delBan(self._jail, ip)
# Find the ticket with the IP.
ticket = self.__banManager.getTicketByID(ip)
ticket = self.banManager.getTicketByID(ip)
if ticket is not None:
# Unban the IP.
self.__unBan(ticket)
@@ -288,7 +291,7 @@ class Actions(JailThread, Mapping):
if not isinstance(ip, IPAddr):
ipa = IPAddr(ip)
if not ipa.isSingle: # subnet (mask/cidr) or raw (may be dns/hostname):
ips = filter(ipa.contains, self.__banManager.getBanList())
ips = filter(ipa.contains, self.banManager.getBanList())
if ips:
return self.removeBannedIP(ips, db, ifexists)
# not found:
@@ -347,7 +350,7 @@ class Actions(JailThread, Mapping):
continue
# wait for ban (stop if gets inactive, pending ban or unban):
bancnt = 0
wt = min(self.sleeptime, self.__banManager._nextUnbanTime - MyTime.time())
wt = min(self.sleeptime, self.banManager._nextUnbanTime - MyTime.time())
logSys.log(5, "Actions: wait for pending tickets %s (default %s)", wt, self.sleeptime)
if Utils.wait_for(lambda: not self.active or self._jail.hasFailTickets, wt):
bancnt = self.__checkBan()
@@ -394,7 +397,12 @@ class Actions(JailThread, Mapping):
"ipfailures": lambda self: self._mi4ip(True).getAttempt(),
"ipjailfailures": lambda self: self._mi4ip().getAttempt(),
# raw ticket info:
"raw-ticket": lambda self: repr(self.__ticket)
"raw-ticket": lambda self: repr(self.__ticket),
# jail info:
"jail.banned": lambda self: self.__jail.actions.banManager.size(),
"jail.banned_total": lambda self: self.__jail.actions.banManager.getBanTotal(),
"jail.found": lambda self: self.__jail.filter.failManager.size(),
"jail.found_total": lambda self: self.__jail.filter.failManager.getFailTotal()
}
__slots__ = CallingMap.__slots__ + ('__ticket', '__jail', '__mi4ip')
@@ -491,11 +499,11 @@ class Actions(JailThread, Mapping):
for ticket in tickets:
bTicket = BanTicket.wrap(ticket)
btime = ticket.getBanTime(self.__banManager.getBanTime())
btime = ticket.getBanTime(self.banManager.getBanTime())
ip = bTicket.getIP()
aInfo = self._getActionInfo(bTicket)
reason = {}
if self.__banManager.addBanTicket(bTicket, reason=reason):
if self.banManager.addBanTicket(bTicket, reason=reason):
cnt += 1
# report ticket to observer, to check time should be increased and hereafter observer writes ban to database (asynchronous)
if Observers.Main is not None and not bTicket.restored:
@@ -554,7 +562,7 @@ class Actions(JailThread, Mapping):
# and increase ticket time if "bantime.increment" set)
if cnt:
logSys.debug("Banned %s / %s, %s ticket(s) in %r", cnt,
self.__banManager.getBanTotal(), self.__banManager.size(), self._jail.name)
self.banManager.getBanTotal(), self.banManager.size(), self._jail.name)
return cnt
def __reBan(self, ticket, actions=None, log=True):
@@ -594,7 +602,7 @@ class Actions(JailThread, Mapping):
def _prolongBan(self, ticket):
# prevent to prolong ticket that was removed in-between,
# if it in ban list - ban time already prolonged (and it stays there):
if not self.__banManager._inBanList(ticket): return
if not self.banManager._inBanList(ticket): return
# do actions :
aInfo = None
for name, action in self._actions.iteritems():
@@ -619,13 +627,13 @@ class Actions(JailThread, Mapping):
Unban IP addresses which are outdated.
"""
lst = self.__banManager.unBanList(MyTime.time(), maxCount)
lst = self.banManager.unBanList(MyTime.time(), maxCount)
for ticket in lst:
self.__unBan(ticket)
cnt = len(lst)
if cnt:
logSys.debug("Unbanned %s, %s ticket(s) in %r",
cnt, self.__banManager.size(), self._jail.name)
cnt, self.banManager.size(), self._jail.name)
return cnt
def __flushBan(self, db=False, actions=None, stop=False):
@@ -639,10 +647,10 @@ class Actions(JailThread, Mapping):
log = True
if actions is None:
logSys.debug(" Flush ban list")
lst = self.__banManager.flushBanList()
lst = self.banManager.flushBanList()
else:
log = False # don't log "[jail] Unban ..." if removing actions only.
lst = iter(self.__banManager)
lst = iter(self.banManager)
cnt = 0
# first we'll execute flush for actions supporting this operation:
unbactions = {}
@@ -679,7 +687,7 @@ class Actions(JailThread, Mapping):
self.__unBan(ticket, actions=actions, log=log)
cnt += 1
logSys.debug(" Unbanned %s, %s ticket(s) in %r",
cnt, self.__banManager.size(), self._jail.name)
cnt, self.banManager.size(), self._jail.name)
return cnt
def __unBan(self, ticket, actions=None, log=True):
@@ -722,18 +730,18 @@ class Actions(JailThread, Mapping):
logSys.warning("Unsupported extended jail status flavor %r. Supported: %s" % (flavor, supported_flavors))
# Always print this information (basic)
if flavor != "short":
banned = self.__banManager.getBanList()
banned = self.banManager.getBanList()
cnt = len(banned)
else:
cnt = self.__banManager.size()
cnt = self.banManager.size()
ret = [("Currently banned", cnt),
("Total banned", self.__banManager.getBanTotal())]
("Total banned", self.banManager.getBanTotal())]
if flavor != "short":
ret += [("Banned IP list", banned)]
if flavor == "cymru":
cymru_info = self.__banManager.getBanListExtendedCymruInfo()
cymru_info = self.banManager.getBanListExtendedCymruInfo()
ret += \
[("Banned ASN list", self.__banManager.geBanListExtendedASN(cymru_info)),
("Banned Country list", self.__banManager.geBanListExtendedCountry(cymru_info)),
("Banned RIR list", self.__banManager.geBanListExtendedRIR(cymru_info))]
[("Banned ASN list", self.banManager.geBanListExtendedASN(cymru_info)),
("Banned Country list", self.banManager.geBanListExtendedCountry(cymru_info)),
("Banned RIR list", self.banManager.geBanListExtendedRIR(cymru_info))]
return ret
+1 -1
View File
@@ -502,7 +502,7 @@ class Fail2BanDb(object):
except TypeError:
firstLineMD5 = None
if not firstLineMD5 and (pos or md5):
if firstLineMD5 is None and (pos or md5 is not None):
cur.execute(
"INSERT OR REPLACE INTO logs(jail, path, firstlinemd5, lastfilepos) "
"VALUES(?, ?, ?, ?)", (jail.name, name, md5, pos))
+3 -3
View File
@@ -35,7 +35,7 @@ from ..helpers import getLogger
# Gets the instance of the logger.
logSys = getLogger(__name__)
logLevel = 6
logLevel = 5
RE_DATE_PREMATCH = re.compile(r"(?<!\\)\{DATE\}", re.IGNORECASE)
DD_patternCache = Utils.Cache(maxCount=1000, maxTime=60*60)
@@ -365,10 +365,10 @@ class DateDetector(object):
# with space or some special char), otherwise possible collision/pattern switch:
if ((
line[distance-1:distance] == self.__lastPos[1] or
(line[distance] == self.__lastPos[2] and not self.__lastPos[2].isalnum())
(line[distance:distance+1] == self.__lastPos[2] and not self.__lastPos[2].isalnum())
) and (
line[endpos:endpos+1] == self.__lastEndPos[2] or
(line[endpos-1] == self.__lastEndPos[1] and not self.__lastEndPos[1].isalnum())
(line[endpos-1:endpos] == self.__lastEndPos[1] and not self.__lastEndPos[1].isalnum())
)):
# search in line part only:
log(logLevel-1, " boundaries are correct, search in part %r", line[distance:endpos])
+2 -1
View File
@@ -136,7 +136,7 @@ class DateTemplate(object):
# remove possible special pattern "**" in front and end of regex:
regex = RE_DEL_WRD_BOUNDS[0].sub(RE_DEL_WRD_BOUNDS[1], regex)
self._regex = regex
logSys.log(7, ' constructed regex %s', regex)
logSys.log(4, ' constructed regex %s', regex)
self._cRegex = None
regex = property(getRegex, setRegex, doc=
@@ -159,6 +159,7 @@ class DateTemplate(object):
"""
if not self._cRegex:
self._compileRegex()
logSys.log(4, " search %s", self.regex)
dateMatch = self._cRegex.search(line, *args); # pos, endpos
if dateMatch:
self.hits += 1
+3 -2
View File
@@ -127,9 +127,10 @@ class FailManager:
return len(self.__failList)
def cleanup(self, time):
time -= self.__maxTime
with self.__lock:
todelete = [fid for fid,item in self.__failList.iteritems() \
if item.getTime() + self.__maxTime <= time]
if item.getTime() <= time]
if len(todelete) == len(self.__failList):
# remove all:
self.__failList = dict()
@@ -143,7 +144,7 @@ class FailManager:
else:
# create new dictionary without items to be deleted:
self.__failList = dict((fid,item) for fid,item in self.__failList.iteritems() \
if item.getTime() + self.__maxTime > time)
if item.getTime() > time)
self.__bgSvc.service()
def delFailure(self, fid):
+236 -83
View File
@@ -94,6 +94,8 @@ class Filter(JailThread):
## Store last time stamp, applicable for multi-line
self.__lastTimeText = ""
self.__lastDate = None
## Next service (cleanup) time
self.__nextSvcTime = -(1<<63)
## if set, treat log lines without explicit time zone to be in this time zone
self.__logtimezone = None
## Default or preferred encoding (to decode bytes from file or journal):
@@ -103,6 +105,10 @@ class Filter(JailThread):
## Error counter (protected, so can be used in filter implementations)
## if it reached 100 (at once), run-cycle will go idle
self._errors = 0
## Next time to update log or journal position in database:
self._nextUpdateTM = 0
## Pending updates (must be executed at next update time or during stop):
self._pendDBUpdates = {}
## return raw host (host is not dns):
self.returnRawHost = False
## check each regex (used for test purposes):
@@ -115,10 +121,10 @@ class Filter(JailThread):
self.checkFindTime = True
## shows that filter is in operation mode (processing new messages):
self.inOperation = True
## if true prevents against retarded banning in case of RC by too many failures (disabled only for test purposes):
self.banASAP = True
## Ticks counter
self.ticks = 0
## Processed lines counter
self.procLines = 0
## Thread name:
self.name="f2b/f."+self.jailName
@@ -442,12 +448,23 @@ class Filter(JailThread):
def performBan(self, ip=None):
"""Performs a ban for IPs (or given ip) that are reached maxretry of the jail."""
try: # pragma: no branch - exception is the only way out
while True:
while True:
try:
ticket = self.failManager.toBan(ip)
self.jail.putFailTicket(ticket)
except FailManagerEmpty:
self.failManager.cleanup(MyTime.time())
except FailManagerEmpty:
break
self.jail.putFailTicket(ticket)
if ip: break
self.performSvc()
def performSvc(self, force=False):
"""Performs a service tasks (clean failure list)."""
tm = MyTime.time()
# avoid too early clean up:
if force or tm >= self.__nextSvcTime:
self.__nextSvcTime = tm + 5
# clean up failure list:
self.failManager.cleanup(tm)
def addAttempt(self, ip, *matches):
"""Generate a failed attempt for ip"""
@@ -606,6 +623,7 @@ class Filter(JailThread):
noDate = False
if date:
tupleLine = line
line = "".join(line)
self.__lastTimeText = tupleLine[1]
self.__lastDate = date
else:
@@ -642,30 +660,37 @@ class Filter(JailThread):
if self.__lastDate and self.__lastDate > MyTime.time() - 60:
tupleLine = ("", self.__lastTimeText, line)
date = self.__lastDate
elif self.checkFindTime and self.inOperation:
date = MyTime.time()
if self.checkFindTime:
if self.checkFindTime and date is not None:
# if in operation (modifications have been really found):
if self.inOperation:
# if weird date - we'd simulate now for timeing issue (too large deviation from now):
if (date is None or date < MyTime.time() - 60 or date > MyTime.time() + 60):
# log time zone issue as warning once per day:
delta = int(date - MyTime.time())
if abs(delta) > 60:
# log timing issue as warning once per day:
self._logWarnOnce("_next_simByTimeWarn",
("Simulate NOW in operation since found time has too large deviation %s ~ %s +/- %s",
date, MyTime.time(), 60),
("Please check jail has possibly a timezone issue. Line with odd timestamp: %s",
line))
("Detected a log entry %s %s the current time in operation mode. "
"This looks like a %s problem. Treating such entries as if they just happened.",
MyTime.seconds2str(abs(delta)), "before" if delta < 0 else "after",
"latency" if -3300 <= delta < 0 else "timezone"
),
("Please check a jail for a timing issue. Line with odd timestamp: %s",
line))
# simulate now as date:
date = MyTime.time()
self.__lastDate = date
else:
# in initialization (restore) phase, if too old - ignore:
if date is not None and date < MyTime.time() - self.getFindTime():
if date < MyTime.time() - self.getFindTime():
# log time zone issue as warning once per day:
self._logWarnOnce("_next_ignByTimeWarn",
("Ignore line since time %s < %s - %s",
date, MyTime.time(), self.getFindTime()),
("Please check jail has possibly a timezone issue. Line with odd timestamp: %s",
line))
("Ignoring all log entries older than %ss; these are probably" +
" messages generated while fail2ban was not running.",
self.getFindTime()),
("Please check a jail for a timing issue. Line with odd timestamp: %s",
line))
# ignore - too old (obsolete) entry:
return []
@@ -695,11 +720,15 @@ class Filter(JailThread):
attempts = self.failManager.addFailure(tick)
# avoid RC on busy filter (too many failures) - if attempts for IP/ID reached maxretry,
# we can speedup ban, so do it as soon as possible:
if self.banASAP and attempts >= self.failManager.getMaxRetry():
if attempts >= self.failManager.getMaxRetry():
self.performBan(ip)
# report to observer - failure was found, for possibly increasing of it retry counter (asynchronous)
if Observers.Main is not None:
Observers.Main.add('failureFound', self.failManager, self.jail, tick)
self.procLines += 1
# every 100 lines check need to perform service tasks:
if self.procLines % 100 == 0:
self.performSvc()
# reset (halve) error counter (successfully processed line):
if self._errors:
self._errors //= 2
@@ -709,7 +738,7 @@ class Filter(JailThread):
# incr common error counter:
self.commonError()
def commonError(self):
def commonError(self, reason="common", exc=None):
# incr error counter, stop processing (going idle) after 100th error :
self._errors += 1
# sleep a little bit (to get around time-related errors):
@@ -1002,9 +1031,6 @@ class FileFilter(Filter):
log = self.__logs.pop(path)
except KeyError:
return
db = self.jail.database
if db is not None:
db.updateLog(self.jail, log)
logSys.info("Removed logfile: %r", path)
self._delLogPath(path)
return
@@ -1068,6 +1094,7 @@ class FileFilter(Filter):
# is created and is added to the FailManager.
def getFailures(self, filename, inOperation=None):
if self.idle: return False
log = self.getLog(filename)
if log is None:
logSys.error("Unable to get failures in %s", filename)
@@ -1113,19 +1140,25 @@ class FileFilter(Filter):
while not self.idle:
line = log.readline()
if not self.active: break; # jail has been stopped
if not line:
if line is None:
# The jail reached the bottom, simply set in operation for this log
# (since we are first time at end of file, growing is only possible after modifications):
log.inOperation = True
break
# acquire in operation from log and process:
self.inOperation = inOperation if inOperation is not None else log.inOperation
self.processLineAndAdd(line.rstrip('\r\n'))
self.processLineAndAdd(line)
finally:
log.close()
db = self.jail.database
if db is not None:
db.updateLog(self.jail, log)
if self.jail.database is not None:
self._pendDBUpdates[log] = 1
if (
self.ticks % 100 == 0
or MyTime.time() >= self._nextUpdateTM
or not self.active
):
self._updateDBPending()
self._nextUpdateTM = MyTime.time() + Utils.DEFAULT_SLEEP_TIME * 5
return True
##
@@ -1137,6 +1170,8 @@ class FileFilter(Filter):
if logSys.getEffectiveLevel() <= logging.DEBUG:
logSys.debug("Seek to find time %s (%s), file size %s", date,
MyTime.time2str(date), fs)
if not fs:
return
minp = container.getPos()
maxp = fs
tryPos = minp
@@ -1160,8 +1195,8 @@ class FileFilter(Filter):
dateTimeMatch = None
nextp = None
while True:
line = container.readline()
if not line:
line = container.readline(False)
if line is None:
break
(timeMatch, template) = self.dateDetector.matchTime(line)
if timeMatch:
@@ -1225,12 +1260,33 @@ class FileFilter(Filter):
ret.append(("File list", path))
return ret
def stop(self):
"""Stop monitoring of log-file(s)
def _updateDBPending(self):
"""Apply pending updates (log position) to database.
"""
db = self.jail.database
while True:
try:
log, args = self._pendDBUpdates.popitem()
except KeyError:
break
db.updateLog(self.jail, log)
def onStop(self):
"""Stop monitoring of log-file(s). Invoked after run method.
"""
# ensure positions of pending logs are up-to-date:
if self._pendDBUpdates and self.jail.database:
self._updateDBPending()
# stop files monitoring:
for path in self.__logs.keys():
self.delLogPath(path)
def stop(self):
"""Stop filter
"""
# normally onStop will be called automatically in thread after its run ends,
# but for backwards compatibilities we'll invoke it in caller of stop method.
self.onStop()
# stop thread:
super(Filter, self).stop()
@@ -1258,34 +1314,56 @@ except ImportError: # pragma: no cover
class FileContainer:
def __init__(self, filename, encoding, tail=False):
def __init__(self, filename, encoding, tail=False, doOpen=False):
self.__filename = filename
self.waitForLineEnd = True
self.setEncoding(encoding)
self.__tail = tail
self.__handler = None
self.__pos = 0
self.__pos4hash = 0
self.__hash = ''
self.__hashNextTime = time.time() + 30
# Try to open the file. Raises an exception if an error occurred.
handler = open(filename, 'rb')
stats = os.fstat(handler.fileno())
self.__ino = stats.st_ino
if doOpen: # fail2ban-regex only (don't need to reopen it and check for rotation)
self.__handler = handler
return
try:
firstLine = handler.readline()
# Computes the MD5 of the first line.
self.__hash = md5sum(firstLine).hexdigest()
# Start at the beginning of file if tail mode is off.
if tail:
handler.seek(0, 2)
self.__pos = handler.tell()
else:
self.__pos = 0
stats = os.fstat(handler.fileno())
self.__ino = stats.st_ino
if stats.st_size:
firstLine = handler.readline()
# first line available and contains new-line:
if firstLine != firstLine.rstrip(b'\r\n'):
# Computes the MD5 of the first line.
self.__hash = md5sum(firstLine).hexdigest()
# if tail mode scroll to the end of file
if tail:
handler.seek(0, 2)
self.__pos = handler.tell()
finally:
handler.close()
## shows that log is in operation mode (expecting new messages only from here):
self.inOperation = tail
def __hash__(self):
return hash(self.__filename)
def __eq__(self, other):
return (id(self) == id(other) or
self.__filename == (other.__filename if isinstance(other, FileContainer) else other)
)
def __repr__(self):
return 'file-log:'+self.__filename
def getFileName(self):
return self.__filename
def getFileSize(self):
h = self.__handler
if h is not None:
stats = os.fstat(h.fileno())
return stats.st_size
return os.path.getsize(self.__filename);
def setEncoding(self, encoding):
@@ -1304,38 +1382,54 @@ class FileContainer:
def setPos(self, value):
self.__pos = value
def open(self):
self.__handler = open(self.__filename, 'rb')
# Set the file descriptor to be FD_CLOEXEC
fd = self.__handler.fileno()
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)
# Stat the file before even attempting to read it
stats = os.fstat(self.__handler.fileno())
if not stats.st_size:
# yoh: so it is still an empty file -- nothing should be
# read from it yet
# print "D: no content -- return"
return False
firstLine = self.__handler.readline()
# Computes the MD5 of the first line.
myHash = md5sum(firstLine).hexdigest()
## print "D: fn=%s hashes=%s/%s inos=%s/%s pos=%s rotate=%s" % (
## self.__filename, self.__hash, myHash, stats.st_ino, self.__ino, self.__pos,
## self.__hash != myHash or self.__ino != stats.st_ino)
## sys.stdout.flush()
# Compare hash and inode
if self.__hash != myHash or self.__ino != stats.st_ino:
logSys.log(logging.MSG, "Log rotation detected for %s", self.__filename)
self.__hash = myHash
self.__ino = stats.st_ino
self.__pos = 0
# Sets the file pointer to the last position.
self.__handler.seek(self.__pos)
def open(self, forcePos=None):
h = open(self.__filename, 'rb')
try:
# Set the file descriptor to be FD_CLOEXEC
fd = h.fileno()
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)
myHash = self.__hash
# Stat the file before even attempting to read it
stats = os.fstat(h.fileno())
rotflg = stats.st_size < self.__pos or stats.st_ino != self.__ino
if rotflg or not len(myHash) or time.time() > self.__hashNextTime:
myHash = ''
firstLine = h.readline()
# Computes the MD5 of the first line (if it is complete)
if firstLine != firstLine.rstrip(b'\r\n'):
myHash = md5sum(firstLine).hexdigest()
self.__hashNextTime = time.time() + 30
elif stats.st_size == self.__pos:
myHash = self.__hash
# Compare size, hash and inode
if rotflg or myHash != self.__hash:
if self.__hash != '':
logSys.log(logging.MSG, "Log rotation detected for %s, reason: %r", self.__filename,
(stats.st_size, self.__pos, stats.st_ino, self.__ino, myHash, self.__hash))
self.__ino = stats.st_ino
self.__pos = 0
self.__hash = myHash
# if nothing to read from file yet (empty or no new data):
if forcePos is not None:
self.__pos = forcePos
elif stats.st_size <= self.__pos:
return False
# Sets the file pointer to the last position.
h.seek(self.__pos)
# leave file open (to read content):
self.__handler = h; h = None
finally:
# close (no content or error only)
if h:
h.close(); h = None
return True
def seek(self, offs, endLine=True):
h = self.__handler
if h is None:
self.open(offs)
h = self.__handler
# seek to given position
h.seek(offs, 0)
# goto end of next line
@@ -1353,6 +1447,9 @@ class FileContainer:
try:
return line.decode(enc, 'strict')
except (UnicodeDecodeError, UnicodeEncodeError) as e:
# avoid warning if got incomplete end of line (e. g. '\n' in "...[0A" followed by "00]..." for utf-16le:
if (e.end == len(line) and line[e.start] in b'\r\n'):
return line[0:e.start].decode(enc, 'replace')
global _decode_line_warn
lev = 7
if not _decode_line_warn.get(filename, 0):
@@ -1361,29 +1458,85 @@ class FileContainer:
logSys.log(lev,
"Error decoding line from '%s' with '%s'.", filename, enc)
if logSys.getEffectiveLevel() <= lev:
logSys.log(lev, "Consider setting logencoding=utf-8 (or another appropriate"
" encoding) for this jail. Continuing"
" to process line ignoring invalid characters: %r",
logSys.log(lev,
"Consider setting logencoding to appropriate encoding for this jail. "
"Continuing to process line ignoring invalid characters: %r",
line)
# decode with replacing error chars:
line = line.decode(enc, 'replace')
return line
def readline(self):
def readline(self, complete=True):
"""Read line from file
In opposite to pythons readline it doesn't return new-line,
so returns either the line if line is complete (and complete=True) or None
if line is not complete (and complete=True) or there is no content to read.
If line is complete (and complete is True), it also shift current known
position to begin of next line.
Also it is safe against interim new-line bytes (e. g. part of multi-byte char)
in given encoding.
"""
if self.__handler is None:
return ""
return FileContainer.decode_line(
self.getFileName(), self.getEncoding(), self.__handler.readline())
# read raw bytes up to \n char:
b = self.__handler.readline()
if not b:
return None
bl = len(b)
# convert to log-encoding (new-line char could disappear if it is part of multi-byte sequence):
r = FileContainer.decode_line(
self.getFileName(), self.getEncoding(), b)
# trim new-line at end and check the line was written complete (contains a new-line):
l = r.rstrip('\r\n')
if complete:
if l == r:
# try to fill buffer in order to find line-end in log encoding:
fnd = 0
while 1:
r = self.__handler.readline()
if not r:
break
b += r
bl += len(r)
# convert to log-encoding:
r = FileContainer.decode_line(
self.getFileName(), self.getEncoding(), b)
# ensure new-line is not in the middle (buffered 2 strings, e. g. in utf-16le it is "...[0A"+"00]..."):
e = r.find('\n')
if e >= 0 and e != len(r)-1:
l, r = r[0:e], r[0:e+1]
# back to bytes and get offset to seek after NL:
r = r.encode(self.getEncoding(), 'replace')
self.__handler.seek(-bl+len(r), 1)
return l
# trim new-line at end and check the line was written complete (contains a new-line):
l = r.rstrip('\r\n')
if l != r:
return l
if self.waitForLineEnd:
# not fulfilled - seek back and return:
self.__handler.seek(-bl, 1)
return None
return l
def close(self):
if not self.__handler is None:
# Saves the last position.
if self.__handler is not None:
# Saves the last real position.
self.__pos = self.__handler.tell()
# Closes the file.
self.__handler.close()
self.__handler = None
## print "D: Closed %s with pos %d" % (handler, self.__pos)
## sys.stdout.flush()
def __iter__(self):
return self
def next(self):
line = self.readline()
if line is None:
self.close()
raise StopIteration
return line
_decode_line_warn = Utils.Cache(maxCount=1000, maxTime=24*60*60);
+3 -13
View File
@@ -55,7 +55,6 @@ class FilterGamin(FileFilter):
def __init__(self, jail):
FileFilter.__init__(self, jail)
self.__modified = False
# Gamin monitor
self.monitor = gamin.WatchMonitor()
fd = self.monitor.get_fd()
@@ -67,21 +66,9 @@ class FilterGamin(FileFilter):
logSys.log(4, "Got event: " + repr(event) + " for " + path)
if event in (gamin.GAMCreated, gamin.GAMChanged, gamin.GAMExists):
logSys.debug("File changed: " + path)
self.__modified = True
self.ticks += 1
self._process_file(path)
def _process_file(self, path):
"""Process a given file
TODO -- RF:
this is a common logic and must be shared/provided by FileFilter
"""
self.getFailures(path)
if not self.banASAP: # pragma: no cover
self.performBan()
self.__modified = False
##
# Add a log file path
@@ -128,6 +115,9 @@ class FilterGamin(FileFilter):
Utils.wait_for(lambda: not self.active or self._handleEvents(),
self.sleeptime)
self.ticks += 1
if self.ticks % 10 == 0:
self.performSvc()
logSys.debug("[%s] filter terminated", self.jailName)
return True
+3 -9
View File
@@ -27,9 +27,7 @@ __license__ = "GPL"
import os
import time
from .failmanager import FailManagerEmpty
from .filter import FileFilter
from .mytime import MyTime
from .utils import Utils
from ..helpers import getLogger, logging
@@ -55,7 +53,6 @@ class FilterPoll(FileFilter):
def __init__(self, jail):
FileFilter.__init__(self, jail)
self.__modified = False
## The time of the last modification of the file.
self.__prevStats = dict()
self.__file404Cnt = dict()
@@ -115,20 +112,17 @@ class FilterPoll(FileFilter):
break
for filename in modlst:
self.getFailures(filename)
self.__modified = True
self.ticks += 1
if self.__modified:
if not self.banASAP: # pragma: no cover
self.performBan()
self.__modified = False
if self.ticks % 10 == 0:
self.performSvc()
except Exception as e: # pragma: no cover
if not self.active: # if not active - error by stop...
break
logSys.error("Caught unhandled exception in main cycle: %r", e,
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
# incr common error counter:
self.commonError()
self.commonError("unhandled", e)
logSys.debug("[%s] filter terminated", self.jailName)
return True
+25 -23
View File
@@ -75,7 +75,6 @@ class FilterPyinotify(FileFilter):
def __init__(self, jail):
FileFilter.__init__(self, jail)
self.__modified = False
# Pyinotify watch manager
self.__monitor = pyinotify.WatchManager()
self.__notifier = None
@@ -140,9 +139,6 @@ class FilterPyinotify(FileFilter):
"""
if not self.idle:
self.getFailures(path)
if not self.banASAP: # pragma: no cover
self.performBan()
self.__modified = False
def _addPending(self, path, reason, isDir=False):
if path not in self.__pending:
@@ -169,7 +165,7 @@ class FilterPyinotify(FileFilter):
return
found = {}
minTime = 60
for path, (retardTM, isDir) in self.__pending.iteritems():
for path, (retardTM, isDir) in list(self.__pending.items()):
if ntm - self.__pendingChkTime < retardTM:
if minTime > retardTM: minTime = retardTM
continue
@@ -192,7 +188,7 @@ class FilterPyinotify(FileFilter):
self._refreshWatcher(path, isDir=isDir)
if isDir:
# check all files belong to this dir:
for logpath in self.__watchFiles:
for logpath in list(self.__watchFiles):
if logpath.startswith(path + pathsep):
# if still no file - add to pending, otherwise refresh and process:
if not os.path.isfile(logpath):
@@ -272,15 +268,13 @@ class FilterPyinotify(FileFilter):
def _addLogPath(self, path):
self._addFileWatcher(path)
# initial scan:
# notify (wake up if in waiting):
if self.active:
# we can execute it right now:
self._process_file(path)
else:
# retard until filter gets started, isDir=None signals special case: process file only (don't need to refresh monitor):
self._addPending(path, ('INITIAL', path), isDir=None)
self.__pendingMinTime = 0
# retard until filter gets started, isDir=None signals special case: process file only (don't need to refresh monitor):
self._addPending(path, ('INITIAL', path), isDir=None)
##
##
# Delete a log path
#
# @param path the log file to delete
@@ -291,7 +285,7 @@ class FilterPyinotify(FileFilter):
logSys.error("Failed to remove watch on path: %s", path)
path_dir = dirname(path)
for k in self.__watchFiles:
for k in list(self.__watchFiles):
if k.startswith(path_dir + pathsep):
path_dir = None
break
@@ -345,16 +339,26 @@ class FilterPyinotify(FileFilter):
self.__notifier.process_events()
# wait for events / timeout:
notify_maxtout = self.__notify_maxtout
def __check_events():
return not self.active or self.__notifier.check_events(timeout=notify_maxtout)
if Utils.wait_for(__check_events, min(self.sleeptime, self.__pendingMinTime)):
return (
not self.active
or bool(self.__notifier.check_events(timeout=self.__notify_maxtout))
or (self.__pendingMinTime and self.__pending)
)
wres = Utils.wait_for(__check_events, min(self.sleeptime, self.__pendingMinTime))
if wres:
if not self.active: break
self.__notifier.read_events()
if not isinstance(wres, dict):
self.__notifier.read_events()
self.ticks += 1
# check pending files/dirs (logrotate ready):
if not self.idle:
self._checkPending()
if self.idle:
continue
self._checkPending()
if self.ticks % 10 == 0:
self.performSvc()
except Exception as e: # pragma: no cover
if not self.active: # if not active - error by stop...
@@ -362,10 +366,8 @@ class FilterPyinotify(FileFilter):
logSys.error("Caught unhandled exception in main cycle: %r", e,
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
# incr common error counter:
self.commonError()
self.commonError("unhandled", e)
self.ticks += 1
logSys.debug("[%s] filter exited (pyinotifier)", self.jailName)
self.__notifier = None
+104 -29
View File
@@ -23,6 +23,7 @@ __copyright__ = "Copyright (c) 2013 Steven Hiscocks"
__license__ = "GPL"
import datetime
import os
import time
from distutils.version import LooseVersion
@@ -91,9 +92,14 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
try:
args['flags'] = int(kwargs.pop('journalflags'))
except KeyError:
# be sure all journal types will be opened if files specified (don't set flags):
if 'files' not in args or not len(args['files']):
args['flags'] = 4
# be sure all journal types will be opened if files/path specified (don't set flags):
if ('files' not in args or not len(args['files'])) and ('path' not in args or not args['path']):
args['flags'] = int(os.getenv("F2B_SYSTEMD_DEFAULT_FLAGS", 4))
try:
args['namespace'] = kwargs.pop('namespace')
except KeyError:
pass
return args
@@ -252,6 +258,10 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
date = datetime.datetime.fromtimestamp(date)
self.__journal.seek_realtime(date)
def inOperationMode(self):
self.inOperation = True
logSys.info("[%s] Jail is in operation now (process new journal entries)", self.jailName)
##
# Main loop.
#
@@ -262,24 +272,46 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
if not self.getJournalMatch():
logSys.notice(
"Jail started without 'journalmatch' set. "
"[%s] Jail started without 'journalmatch' set. "
"Jail regexs will be checked against all journal entries, "
"which is not advised for performance reasons.")
"which is not advised for performance reasons.", self.jailName)
# Try to obtain the last known time (position of journal)
start_time = 0
if self.jail.database is not None:
start_time = self.jail.database.getJournalPos(self.jail, 'systemd-journal') or 0
# Seek to max(last_known_time, now - findtime) in journal
start_time = max( start_time, MyTime.time() - int(self.getFindTime()) )
self.seekToTime(start_time)
# Move back one entry to ensure do not end up in dead space
# if start time beyond end of journal
# Save current cursor position (to recognize in operation mode):
logentry = None
try:
self.__journal.get_previous()
self.__journal.seek_tail()
logentry = self.__journal.get_previous()
self.__journal.get_next()
except OSError:
pass # Reading failure, so safe to ignore
logentry = None # Reading failure, so safe to ignore
if logentry:
# Try to obtain the last known time (position of journal)
startTime = 0
if self.jail.database is not None:
startTime = self.jail.database.getJournalPos(self.jail, 'systemd-journal') or 0
# Seek to max(last_known_time, now - findtime) in journal
startTime = max( startTime, MyTime.time() - int(self.getFindTime()) )
self.seekToTime(startTime)
# Not in operation while we'll read old messages ...
self.inOperation = False
# Save current time in order to check time to switch "in operation" mode
startTime = (1, MyTime.time(), logentry.get('__CURSOR'))
# Move back one entry to ensure do not end up in dead space
# if start time beyond end of journal
try:
self.__journal.get_previous()
except OSError:
pass # Reading failure, so safe to ignore
else:
# empty journal or no entries for current filter:
self.inOperationMode()
# seek_tail() seems to have a bug by no entries (could bypass some entries hereafter), so seek to now instead:
startTime = MyTime.time()
self.seekToTime(startTime)
# for possible future switches of in-operation mode:
startTime = (0, startTime)
line = None
while self.active:
# wait for records (or for timeout in sleeptime seconds):
try:
@@ -289,9 +321,10 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
#self.__journal.wait(self.sleeptime) != journal.NOP
##
## wait for entries without sleep in intervals, because "sleeping" in journal.wait:
Utils.wait_for(lambda: not self.active or \
self.__journal.wait(Utils.DEFAULT_SLEEP_INTERVAL) != journal.NOP,
self.sleeptime, 0.00001)
if not logentry:
Utils.wait_for(lambda: not self.active or \
self.__journal.wait(Utils.DEFAULT_SLEEP_INTERVAL) != journal.NOP,
self.sleeptime, 0.00001)
if self.idle:
# because journal.wait will returns immediatelly if we have records in journal,
# just wait a little bit here for not idle, to prevent hi-load:
@@ -310,27 +343,50 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
e, exc_info=logSys.getEffectiveLevel() <= logging.DEBUG)
self.ticks += 1
if logentry:
line = self.formatJournalEntry(logentry)
self.processLineAndAdd(*line)
line, tm = self.formatJournalEntry(logentry)
# switch "in operation" mode if we'll find start entry (+ some delta):
if not self.inOperation:
if tm >= MyTime.time() - 1: # reached now (approximated):
self.inOperationMode()
elif startTime[0] == 1:
# if it reached start entry (or get read time larger than start time)
if logentry.get('__CURSOR') == startTime[2] or tm > startTime[1]:
# give the filter same time it needed to reach the start entry:
startTime = (0, MyTime.time()*2 - startTime[1])
elif tm > startTime[1]: # reached start time (approximated):
self.inOperationMode()
# process line
self.processLineAndAdd(line, tm)
self.__modified += 1
if self.__modified >= 100: # todo: should be configurable
break
else:
# "in operation" mode since we don't have messages anymore (reached end of journal):
if not self.inOperation:
self.inOperationMode()
break
if self.__modified:
if not self.banASAP: # pragma: no cover
self.performBan()
self.__modified = 0
# update position in log (time and iso string):
if self.jail.database is not None:
self.jail.database.updateJournal(self.jail, 'systemd-journal', line[1], line[0][1])
self.__modified = 0
if self.ticks % 10 == 0:
self.performSvc()
# update position in log (time and iso string):
if self.jail.database:
if line:
self._pendDBUpdates['systemd-journal'] = (tm, line[1])
line = None
if self._pendDBUpdates and (
self.ticks % 100 == 0
or MyTime.time() >= self._nextUpdateTM
or not self.active
):
self._updateDBPending()
self._nextUpdateTM = MyTime.time() + Utils.DEFAULT_SLEEP_TIME * 5
except Exception as e: # pragma: no cover
if not self.active: # if not active - error by stop...
break
logSys.error("Caught unhandled exception in main cycle: %r", e,
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
# incr common error counter:
self.commonError()
self.commonError("unhandled", e)
logSys.debug("[%s] filter terminated", self.jailName)
@@ -350,3 +406,22 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
ret.append(("Journal matches",
[" + ".join(" ".join(match) for match in self.__matches)]))
return ret
def _updateDBPending(self):
"""Apply pending updates (jornal position) to database.
"""
db = self.jail.database
while True:
try:
log, args = self._pendDBUpdates.popitem()
except KeyError:
break
db.updateJournal(self.jail, log, *args)
def onStop(self):
"""Stop monitoring of journal. Invoked after run method.
"""
# ensure positions of pending logs are up-to-date:
if self._pendDBUpdates and self.jail.database:
self._updateDBPending()
+31 -10
View File
@@ -169,27 +169,31 @@ class DNSUtils:
DNSUtils.CACHE_ipToName.set(key, name)
return name
# key find cached own hostnames (this tuple-key cannot be used elsewhere):
_getSelfNames_key = ('self','dns')
@staticmethod
def getSelfNames():
"""Get own host names of self"""
# try find cached own hostnames (this tuple-key cannot be used elsewhere):
key = ('self','dns')
names = DNSUtils.CACHE_ipToName.get(key)
# try find cached own hostnames:
names = DNSUtils.CACHE_ipToName.get(DNSUtils._getSelfNames_key)
# get it using different ways (a set with names of localhost, hostname, fully qualified):
if names is None:
names = set([
'localhost', DNSUtils.getHostname(False), DNSUtils.getHostname(True)
]) - set(['']) # getHostname can return ''
# cache and return :
DNSUtils.CACHE_ipToName.set(key, names)
DNSUtils.CACHE_ipToName.set(DNSUtils._getSelfNames_key, names)
return names
# key to find cached own IPs (this tuple-key cannot be used elsewhere):
_getSelfIPs_key = ('self','ips')
@staticmethod
def getSelfIPs():
"""Get own IP addresses of self"""
# try find cached own IPs (this tuple-key cannot be used elsewhere):
key = ('self','ips')
ips = DNSUtils.CACHE_nameToIp.get(key)
# to find cached own IPs:
ips = DNSUtils.CACHE_nameToIp.get(DNSUtils._getSelfIPs_key)
# get it using different ways (a set with IPs of localhost, hostname, fully qualified):
if ips is None:
ips = set()
@@ -199,13 +203,30 @@ class DNSUtils:
except Exception as e: # pragma: no cover
logSys.warning("Retrieving own IPs of %s failed: %s", hostname, e)
# cache and return :
DNSUtils.CACHE_nameToIp.set(key, ips)
DNSUtils.CACHE_nameToIp.set(DNSUtils._getSelfIPs_key, ips)
return ips
_IPv6IsAllowed = None
@staticmethod
def setIPv6IsAllowed(value):
DNSUtils._IPv6IsAllowed = value
logSys.debug("IPv6 is %s", ('on' if value else 'off') if value is not None else 'auto')
return value
# key to find cached value of IPv6 allowance (this tuple-key cannot be used elsewhere):
_IPv6IsAllowed_key = ('self','ipv6-allowed')
@staticmethod
def IPv6IsAllowed():
# return os.path.exists("/proc/net/if_inet6") || any((':' in ip) for ip in DNSUtils.getSelfIPs())
return any((':' in ip.ntoa) for ip in DNSUtils.getSelfIPs())
if DNSUtils._IPv6IsAllowed is not None:
return DNSUtils._IPv6IsAllowed
v = DNSUtils.CACHE_nameToIp.get(DNSUtils._IPv6IsAllowed_key)
if v is not None:
return v
v = any((':' in ip.ntoa) for ip in DNSUtils.getSelfIPs())
DNSUtils.CACHE_nameToIp.set(DNSUtils._IPv6IsAllowed_key, v)
return v
##
+4 -1
View File
@@ -22,7 +22,10 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2013- Yaroslav Halchenko"
__license__ = "GPL"
from threading import Lock
from collections import Mapping
try:
from collections.abc import Mapping
except ImportError:
from collections import Mapping
from ..exceptions import DuplicateJailException, UnknownJailException
from .jail import Jail
+8
View File
@@ -67,6 +67,8 @@ class JailThread(Thread):
def run_with_except_hook(*args, **kwargs):
try:
run(*args, **kwargs)
# call on stop callback to do some finalizations:
self.onStop()
except Exception as e:
# avoid very sporadic error "'NoneType' object has no attribute 'exc_info'" (https://bugs.python.org/issue7336)
# only extremely fast systems are affected ATM (2.7 / 3.x), if thread ends nothing is available here.
@@ -97,6 +99,12 @@ class JailThread(Thread):
self.active = True
super(JailThread, self).start()
@abstractmethod
def onStop(self): # pragma: no cover - absract
"""Abstract - Called when thread ends (after run).
"""
pass
def stop(self):
"""Sets `active` property to False, to flag run method to return.
"""
+59
View File
@@ -174,3 +174,62 @@ class MyTime:
val = rexp.sub(rpl, val)
val = MyTime._str2sec_fini.sub(r"\1+\2", val)
return eval(val)
class seconds2str():
"""Converts seconds to string on demand (if string representation needed).
Ex: seconds2str(86400*390) = 1y 3w 4d
seconds2str(86400*368) = 1y 3d
seconds2str(86400*365.5) = 1y
seconds2str(86400*2+3600*7+60*15) = 2d 7h 15m
seconds2str(86400*2+3599) = 2d 1h
seconds2str(3600-5) = 1h
seconds2str(3600-10) = 59m 50s
seconds2str(59) = 59s
"""
def __init__(self, sec):
self.sec = sec
def __str__(self):
# s = str(datetime.timedelta(seconds=int(self.sec)))
# return s if s[-3:] != ":00" else s[:-3]
s = self.sec; c = 3
# automatic accuracy: round by large values (and maximally 3 groups)
if s >= 31536000: # a year as 365*24*60*60 (don't need to consider leap year by this accuracy)
s = int(round(float(s)/86400)) # round by a day
r = str(s//365) + 'y '; s %= 365
if s >= 7:
r += str(s//7) + 'w '; s %= 7
if s:
r += str(s) + 'd '
return r[:-1]
if s >= 604800: # a week as 24*60*60*7
s = int(round(float(s)/3600)) # round by a hour
r = str(s//168) + 'w '; s %= 168
if s >= 24:
r += str(s//24) + 'd '; s %= 24
if s:
r += str(s) + 'h '
return r[:-1]
if s >= 86400: # a day as 24*60*60
s = int(round(float(s)/60)) # round by a minute
r = str(s//1440) + 'd '; s %= 1440
if s >= 60:
r += str(s//60) + 'h '; s %= 60
if s:
r += str(s) + 'm '
return r[:-1]
if s >= 3595: # a hour as 60*60 (- 5 seconds)
s = int(round(float(s)/10)) # round by 10 seconds
r = str(s//360) + 'h '; s %= 360
if s >= 6: # a minute
r += str(s//6) + 'm '; s %= 6
return r[:-1]
r = ''
if s >= 60: # a minute
r += str(s//60) + 'm '; s %= 60
if s: # remaining seconds
r += str(s) + 's '
elif not self.sec: # 0s
r = '0 '
return r[:-1]
def __repr__(self):
return self.__str__()
+3 -4
View File
@@ -232,7 +232,7 @@ class ObserverThread(JailThread):
if self._paused:
continue
else:
## notify event deleted (shutdown) - just sleep a litle bit (waiting for shutdown events, prevent high cpu usage)
## notify event deleted (shutdown) - just sleep a little bit (waiting for shutdown events, prevent high cpu usage)
time.sleep(ObserverThread.DEFAULT_SLEEP_INTERVAL)
## stop by shutdown and empty queue :
if not self.is_full:
@@ -462,7 +462,7 @@ class ObserverThread(JailThread):
if ticket.getTime() > timeOfBan:
logSys.info('[%s] IP %s is bad: %s # last %s - incr %s to %s' % (jail.name, ip, banCount,
MyTime.time2str(timeOfBan),
datetime.timedelta(seconds=int(orgBanTime)), datetime.timedelta(seconds=int(banTime))));
MyTime.seconds2str(orgBanTime), MyTime.seconds2str(banTime)))
else:
ticket.restored = True
break
@@ -491,8 +491,7 @@ class ObserverThread(JailThread):
# if not permanent
if btime != -1:
bendtime = ticket.getTime() + btime
logtime = (datetime.timedelta(seconds=int(btime)),
MyTime.time2str(bendtime))
logtime = (MyTime.seconds2str(btime), MyTime.time2str(bendtime))
# check ban is not too old :
if bendtime < MyTime.time():
logSys.debug('Ignore old bantime %s', logtime[1])
+13 -1
View File
@@ -34,7 +34,7 @@ import sys
from .observer import Observers, ObserverThread
from .jails import Jails
from .filter import FileFilter, JournalFilter
from .filter import DNSUtils, FileFilter, JournalFilter
from .transmitter import Transmitter
from .asyncserver import AsyncServer, AsyncServerException
from .. import version
@@ -293,6 +293,11 @@ class Server:
for name in self.__jails.keys():
self.delJail(name, stop=False, join=True)
def clearCaches(self):
# we need to clear caches, to be able to recognize new IPs/families etc:
DNSUtils.CACHE_nameToIp.clear()
DNSUtils.CACHE_ipToName.clear()
def reloadJails(self, name, opts, begin):
if begin:
# begin reload:
@@ -314,6 +319,8 @@ class Server:
if "--restart" in opts:
self.stopJail(name)
else:
# invalidate caches by reload
self.clearCaches()
# first unban all ips (will be not restored after (re)start):
if "--unban" in opts:
self.setUnbanIP()
@@ -803,6 +810,11 @@ class Server:
logSys.info("flush performed on %s" % self.__logTarget)
return "flushed"
@staticmethod
def setIPv6IsAllowed(value):
value = _as_bool(value) if value != 'auto' else None
return DNSUtils.setIPv6IsAllowed(value)
def setThreadOptions(self, value):
for o, v in value.iteritems():
if o == 'stacksize':
+61 -28
View File
@@ -30,17 +30,6 @@ locale_time = LocaleTime()
TZ_ABBR_RE = r"[A-Z](?:[A-Z]{2,4})?"
FIXED_OFFSET_TZ_RE = re.compile(r"(%s)?([+-][01]\d(?::?\d{2})?)?$" % (TZ_ABBR_RE,))
def _getYearCentRE(cent=(0,3), distance=3, now=(MyTime.now(), MyTime.alternateNow)):
""" Build century regex for last year and the next years (distance).
Thereby respect possible run in the test-cases (alternate date used there)
"""
cent = lambda year, f=cent[0], t=cent[1]: str(year)[f:t]
exprset = set( cent(now[0].year + i) for i in (-1, distance) )
if len(now) and now[1]:
exprset |= set( cent(now[1].year + i) for i in (-1, distance) )
return "(?:%s)" % "|".join(exprset) if len(exprset) > 1 else "".join(exprset)
timeRE = TimeRE()
# %k - one- or two-digit number giving the hour of the day (0-23) on a 24-hour clock,
@@ -63,20 +52,68 @@ timeRE['z'] = r"(?P<z>Z|UTC|GMT|[+-][01]\d(?::?\d{2})?)"
timeRE['ExZ'] = r"(?P<Z>%s)" % (TZ_ABBR_RE,)
timeRE['Exz'] = r"(?P<z>(?:%s)?[+-][01]\d(?::?\d{2})?|%s)" % (TZ_ABBR_RE, TZ_ABBR_RE)
# overwrite default patterns, since they can be non-optimal:
timeRE['d'] = r"(?P<d>[1-2]\d|[0 ]?[1-9]|3[0-1])"
timeRE['m'] = r"(?P<m>0?[1-9]|1[0-2])"
timeRE['Y'] = r"(?P<Y>\d{4})"
timeRE['H'] = r"(?P<H>[0-1]?\d|2[0-3])"
timeRE['M'] = r"(?P<M>[0-5]?\d)"
timeRE['S'] = r"(?P<S>[0-5]?\d|6[0-1])"
# Extend build-in TimeRE with some exact patterns
# exact two-digit patterns:
timeRE['Exd'] = r"(?P<d>3[0-1]|[1-2]\d|0[1-9])"
timeRE['Exm'] = r"(?P<m>1[0-2]|0[1-9])"
timeRE['ExH'] = r"(?P<H>2[0-3]|[0-1]\d)"
timeRE['Exk'] = r" ?(?P<H>2[0-3]|[0-1]\d|\d)"
timeRE['Exd'] = r"(?P<d>[1-2]\d|0[1-9]|3[0-1])"
timeRE['Exm'] = r"(?P<m>0[1-9]|1[0-2])"
timeRE['ExH'] = r"(?P<H>[0-1]\d|2[0-3])"
timeRE['Exk'] = r" ?(?P<H>[0-1]?\d|2[0-3])"
timeRE['Exl'] = r" ?(?P<I>1[0-2]|\d)"
timeRE['ExM'] = r"(?P<M>[0-5]\d)"
timeRE['ExS'] = r"(?P<S>6[0-1]|[0-5]\d)"
# more precise year patterns, within same century of last year and
# the next 3 years (for possible long uptime of fail2ban); thereby
# respect possible run in the test-cases (alternate date used there):
timeRE['ExY'] = r"(?P<Y>%s\d)" % _getYearCentRE(cent=(0,3), distance=3)
timeRE['Exy'] = r"(?P<y>%s\d)" % _getYearCentRE(cent=(2,3), distance=3)
timeRE['ExS'] = r"(?P<S>[0-5]\d|6[0-1])"
def _updateTimeRE():
def _getYearCentRE(cent=(0,3), distance=3, now=(MyTime.now(), MyTime.alternateNow)):
""" Build century regex for last year and the next years (distance).
Thereby respect possible run in the test-cases (alternate date used there)
"""
cent = lambda year, f=cent[0], t=cent[1]: str(year)[f:t]
def grp(exprset):
c = None
if len(exprset) > 1:
for i in exprset:
if c is None or i[0:-1] == c:
c = i[0:-1]
else:
c = None
break
if not c:
for i in exprset:
if c is None or i[0] == c:
c = i[0]
else:
c = None
break
if c:
return "%s%s" % (c, grp([i[len(c):] for i in exprset]))
return ("(?:%s)" % "|".join(exprset) if len(exprset[0]) > 1 else "[%s]" % "".join(exprset)) \
if len(exprset) > 1 else "".join(exprset)
exprset = set( cent(now[0].year + i) for i in (-1, distance) )
if len(now) > 1 and now[1]:
exprset |= set( cent(now[1].year + i) for i in xrange(-1, now[0].year-now[1].year+1, distance) )
return grp(sorted(list(exprset)))
# more precise year patterns, within same century of last year and
# the next 3 years (for possible long uptime of fail2ban); thereby
# consider possible run in the test-cases (alternate date used there),
# so accept years: 20xx (from test-date or 2001 up to current century)
timeRE['ExY'] = r"(?P<Y>%s\d)" % _getYearCentRE(cent=(0,3), distance=3,
now=(datetime.datetime.now(), datetime.datetime.fromtimestamp(
min(MyTime.alternateNowTime or 978393600, 978393600))
)
)
timeRE['Exy'] = r"(?P<y>\d{2})"
_updateTimeRE()
def getTimePatternRE():
keys = timeRE.keys()
@@ -168,9 +205,9 @@ def reGroupDictStrptime(found_dict, msec=False, default_tz=None):
"""
now = \
year = month = day = hour = minute = tzoffset = \
year = month = day = tzoffset = \
weekday = julian = week_of_year = None
second = fraction = 0
hour = minute = second = fraction = 0
for key, val in found_dict.iteritems():
if val is None: continue
# Directives not explicitly handled below:
@@ -234,16 +271,12 @@ def reGroupDictStrptime(found_dict, msec=False, default_tz=None):
week_of_year = int(val)
# U starts week on Sunday, W - on Monday
week_of_year_start = 6 if key == 'U' else 0
elif key == 'z':
elif key in ('z', 'Z'):
z = val
if z in ("Z", "UTC", "GMT"):
tzoffset = 0
else:
tzoffset = zone2offset(z, 0); # currently offset-based only
elif key == 'Z':
z = val
if z in ("UTC", "GMT"):
tzoffset = 0
# Fail2Ban will assume it's this year
assume_year = False
+5
View File
@@ -173,6 +173,11 @@ class Transmitter:
return self.__server.getSyslogSocket()
else:
raise Exception("Failed to change syslog socket")
elif name == "allowipv6":
value = command[1]
self.__server.setIPv6IsAllowed(value)
if self.__quiet: return
return value
#Thread
elif name == "thread":
value = command[1]
+1 -3
View File
@@ -332,11 +332,9 @@ class Utils():
timeout_expr = lambda: time.time() > time0
else:
timeout_expr = timeout
if not interval:
interval = Utils.DEFAULT_SLEEP_INTERVAL
if timeout_expr():
break
stm = min(stm + interval, Utils.DEFAULT_SLEEP_TIME)
stm = min(stm + (interval or Utils.DEFAULT_SLEEP_INTERVAL), Utils.DEFAULT_SLEEP_TIME)
time.sleep(stm)
return ret
-157
View File
@@ -1,157 +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.
import os
import unittest
import sys
from functools import wraps
from socket import timeout
from ssl import SSLError
from ..actiontestcase import CallingMap
from ..dummyjail import DummyJail
from ..servertestcase import IPAddr
from ..utils import LogCaptureTestCase, CONFIG_DIR
if sys.version_info >= (3, ): # pragma: 2.x no cover
from urllib.error import HTTPError, URLError
else: # pragma: 3.x no cover
from urllib2 import HTTPError, URLError
def skip_if_not_available(f):
"""Helper to decorate tests to skip in case of timeout/http-errors like "502 bad gateway".
"""
@wraps(f)
def wrapper(self, *args):
try:
return f(self, *args)
except (SSLError, HTTPError, URLError, timeout) as e: # pragma: no cover - timeout/availability issues
if not isinstance(e, timeout) and 'timed out' not in str(e):
if not hasattr(e, 'code') or e.code > 200 and e.code <= 404:
raise
raise unittest.SkipTest('Skip test because of %s' % e)
return wrapper
if sys.version_info >= (2,7): # pragma: no cover - may be unavailable
class BadIPsActionTest(LogCaptureTestCase):
available = True, None
pythonModule = None
modAction = None
@skip_if_not_available
def setUp(self):
"""Call before every test case."""
super(BadIPsActionTest, self).setUp()
unittest.F2B.SkipIfNoNetwork()
self.jail = DummyJail()
self.jail.actions.add("test")
pythonModuleName = os.path.join(CONFIG_DIR, "action.d", "badips.py")
# check availability (once if not alive, used shorter timeout as in test cases):
if BadIPsActionTest.available[0]:
if not BadIPsActionTest.modAction:
if not BadIPsActionTest.pythonModule:
BadIPsActionTest.pythonModule = self.jail.actions._load_python_module(pythonModuleName)
BadIPsActionTest.modAction = BadIPsActionTest.pythonModule.Action
self.jail.actions._load_python_module(pythonModuleName)
BadIPsActionTest.available = BadIPsActionTest.modAction.isAvailable(timeout=2 if unittest.F2B.fast else 30)
if not BadIPsActionTest.available[0]:
raise unittest.SkipTest('Skip test because service is not available: %s' % BadIPsActionTest.available[1])
self.jail.actions.add("badips", pythonModuleName, initOpts={
'category': "ssh",
'banaction': "test",
'age': "2w",
'score': 5,
#'key': "fail2ban-test-suite",
#'bankey': "fail2ban-test-suite",
'timeout': (3 if unittest.F2B.fast else 60),
})
self.action = self.jail.actions["badips"]
def tearDown(self):
"""Call after every test case."""
# Must cancel timer!
if self.action._timer:
self.action._timer.cancel()
super(BadIPsActionTest, self).tearDown()
@skip_if_not_available
def testCategory(self):
categories = self.action.getCategories()
self.assertIn("ssh", categories)
self.assertTrue(len(categories) >= 10)
self.assertRaises(
ValueError, setattr, self.action, "category",
"invalid-category")
# Not valid for reporting category...
self.assertRaises(
ValueError, setattr, self.action, "category", "mail")
# but valid for blacklisting.
self.action.bancategory = "mail"
@skip_if_not_available
def testScore(self):
self.assertRaises(ValueError, setattr, self.action, "score", -5)
self.action.score = 3
self.action.score = "3"
@skip_if_not_available
def testBanaction(self):
self.assertRaises(
ValueError, setattr, self.action, "banaction",
"invalid-action")
self.action.banaction = "test"
@skip_if_not_available
def testUpdateperiod(self):
self.assertRaises(
ValueError, setattr, self.action, "updateperiod", -50)
self.assertRaises(
ValueError, setattr, self.action, "updateperiod", 0)
self.action.updateperiod = 900
self.action.updateperiod = "900"
@skip_if_not_available
def testStartStop(self):
self.action.start()
self.assertTrue(len(self.action._bannedips) > 10,
"%s is fewer as 10: %r" % (len(self.action._bannedips), self.action._bannedips))
self.action.stop()
self.assertTrue(len(self.action._bannedips) == 0)
@skip_if_not_available
def testBanIP(self):
aInfo = CallingMap({
'ip': IPAddr('192.0.2.1')
})
self.action.ban(aInfo)
self.assertLogged('badips.com: ban', wait=True)
self.pruneLog()
# produce an error using wrong category/IP:
self.action._category = 'f2b-this-category-dont-available-test-suite-only'
aInfo['ip'] = ''
self.assertRaises(BadIPsActionTest.pythonModule.HTTPError, self.action.ban, aInfo)
self.assertLogged('IP is invalid', 'invalid category', wait=True, all=False)
+6
View File
@@ -217,6 +217,9 @@ class ExecuteActions(LogCaptureTestCase):
# flush for inet6 is intentionally "broken" here - test no unhandled except and invariant check:
act['actionflush?family=inet6'] = act.actionflush + '; exit 1'
act.actionstart_on_demand = True
# force errors via check in ban/unban:
act.actionban = "<actioncheck> ; " + act.actionban
act.actionunban = "<actioncheck> ; " + act.actionunban
self.__actions.start()
self.assertNotLogged("stdout: %r" % 'ip start')
@@ -294,6 +297,9 @@ class ExecuteActions(LogCaptureTestCase):
act['actionflush?family=inet6'] = act.actionflush + '; exit 1'
act.actionstart_on_demand = True
act.actionrepair_on_unban = True
# force errors via check in ban/unban:
act.actionban = "<actioncheck> ; " + act.actionban
act.actionunban = "<actioncheck> ; " + act.actionunban
self.__actions.start()
self.assertNotLogged("stdout: %r" % 'ip start')
+43 -3
View File
@@ -305,8 +305,8 @@ class CommandActionTest(LogCaptureTestCase):
self.assertEqual(self.__action.actionstart, "touch '%s'" % tmp)
self.__action.actionstop = "rm -f '%s'" % tmp
self.assertEqual(self.__action.actionstop, "rm -f '%s'" % tmp)
self.__action.actionban = "echo -n"
self.assertEqual(self.__action.actionban, 'echo -n')
self.__action.actionban = "<actioncheck> && echo -n"
self.assertEqual(self.__action.actionban, "<actioncheck> && echo -n")
self.__action.actioncheck = "[ -e '%s' ]" % tmp
self.assertEqual(self.__action.actioncheck, "[ -e '%s' ]" % tmp)
self.__action.actionunban = "true"
@@ -316,6 +316,7 @@ class CommandActionTest(LogCaptureTestCase):
self.assertNotLogged('returned')
# no action was actually executed yet
# start on demand is false, so it should cause failure on first attempt of ban:
self.__action.ban({'ip': None})
self.assertLogged('Invariant check failed')
self.assertLogged('returned successfully')
@@ -365,12 +366,51 @@ class CommandActionTest(LogCaptureTestCase):
self.pruneLog('[phase 2]')
self.__action.actionstart = "touch '%s'" % tmp
self.__action.actionstop = "rm '%s'" % tmp
self.__action.actionban = """printf "%%%%b\n" <ip> >> '%s'""" % tmp
self.__action.actionban = """<actioncheck> && printf "%%%%b\n" <ip> >> '%s'""" % tmp
self.__action.actioncheck = "[ -e '%s' ]" % tmp
self.__action.ban({'ip': None})
self.assertLogged('Invariant check failed')
self.assertNotLogged('Unable to restore environment')
@with_tmpdir
def testExecuteActionCheckOnBanFailure(self, tmp):
tmp += '/fail2ban.test'
self.__action.actionstart = "touch '%s'; echo 'started ...'" % tmp
self.__action.actionstop = "rm -f '%s'" % tmp
self.__action.actionban = "[ -e '%s' ] && echo 'banned '<ip>" % tmp
self.__action.actioncheck = "[ -e '%s' ] && echo 'check ok' || { echo 'check failed'; exit 1; }" % tmp
self.__action.actionrepair = "echo 'repair ...'; touch '%s'" % tmp
self.__action.actionstart_on_demand = False
self.__action.start()
# phase 1: with repair;
# phase 2: without repair (start/stop), not on demand;
# phase 3: without repair (start/stop), start on demand.
for i in (1, 2, 3):
self.pruneLog('[phase %s]' % i)
# 1st time with success ban:
self.__action.ban({'ip': '192.0.2.1'})
self.assertLogged(
"stdout: %r" % 'banned 192.0.2.1', all=True)
self.assertNotLogged("Invariant check failed. Trying",
"stdout: %r" % 'check failed',
"stdout: %r" % ('repair ...' if self.__action.actionrepair else 'started ...'),
"stdout: %r" % 'check ok', all=True)
# force error in ban:
os.remove(tmp)
self.pruneLog()
# 2nd time with fail recognition, success repair, check and ban:
self.__action.ban({'ip': '192.0.2.2'})
self.assertLogged("Invariant check failed. Trying",
"stdout: %r" % 'check failed',
"stdout: %r" % ('repair ...' if self.__action.actionrepair else 'started ...'),
"stdout: %r" % 'check ok',
"stdout: %r" % 'banned 192.0.2.2', all=True)
# repeat without repair (stop/start), herafter enable on demand:
if self.__action.actionrepair:
self.__action.actionrepair = ""
elif not self.__action.actionstart_on_demand:
self.__action.actionstart_on_demand = True
@with_tmpdir
def testExecuteActionCheckRepairEnvironment(self, tmp):
tmp += '/fail2ban.test'
+5 -4
View File
@@ -29,6 +29,7 @@ import unittest
from .utils import setUpMyTime, tearDownMyTime
from ..server.banmanager import BanManager
from ..server.ipdns import DNSUtils
from ..server.ticket import BanTicket
class AddFailure(unittest.TestCase):
@@ -176,10 +177,10 @@ class StatusExtendedCymruInfo(unittest.TestCase):
super(StatusExtendedCymruInfo, self).setUp()
unittest.F2B.SkipIfNoNetwork()
setUpMyTime()
self.__ban_ip = "93.184.216.34"
self.__asn = "15133"
self.__country = "EU"
self.__rir = "ripencc"
self.__ban_ip = iter(DNSUtils.dnsToIp("resolver1.opendns.com")).next()
self.__asn = "36692"
self.__country = "US"
self.__rir = "arin"
ticket = BanTicket(self.__ban_ip, 1167605999.0)
self.__banManager = BanManager()
self.assertTrue(self.__banManager.addBanTicket(ticket))
+14 -14
View File
@@ -381,13 +381,16 @@ class JailReaderTest(LogCaptureTestCase):
self.assertEqual(('mail.who_is', {'a':'cat', 'b':'dog'}), extractOptions("mail.who_is[a=cat,b=dog]"))
self.assertEqual(('mail--ho_is', {}), extractOptions("mail--ho_is"))
self.assertEqual(('mail--ho_is', {}), extractOptions("mail--ho_is['s']"))
#print(self.getLog())
#self.assertLogged("Invalid argument ['s'] in ''s''")
self.assertEqual(('mail', {'a': ','}), extractOptions("mail[a=',']"))
self.assertEqual(('mail', {'a': 'b'}), extractOptions("mail[a=b, ]"))
#self.assertRaises(ValueError, extractOptions ,'mail-how[')
self.assertRaises(ValueError, extractOptions ,'mail-how[')
self.assertRaises(ValueError, extractOptions, """mail[a="test with interim (wrong) "" quotes"]""")
self.assertRaises(ValueError, extractOptions, """mail[a='test with interim (wrong) '' quotes']""")
self.assertRaises(ValueError, extractOptions, """mail[a='x, y, z', b=x, y, z]""")
self.assertRaises(ValueError, extractOptions, """mail['s']""")
# Empty option
option = "abc[]"
@@ -455,8 +458,6 @@ class JailReaderTest(LogCaptureTestCase):
('sender', 'f2b-test@example.com'), ('blocklist_de_apikey', 'test-key'),
('action',
'%(action_blocklist_de)s\n'
'%(action_badips_report)s\n'
'%(action_badips)s\n'
'mynetwatchman[port=1234,protocol=udp,agent="%(fail2ban_agent)s"]'
),
))
@@ -470,16 +471,14 @@ class JailReaderTest(LogCaptureTestCase):
if len(cmd) <= 4:
continue
# differentiate between set and multi-set (wrop it here to single set):
if cmd[0] == 'set' and (cmd[4] == 'agent' or cmd[4].endswith('badips.py')):
if cmd[0] == 'set' and cmd[4] == 'agent':
act.append(cmd)
elif cmd[0] == 'multi-set':
act.extend([['set'] + cmd[1:4] + o for o in cmd[4] if o[0] == 'agent'])
useragent = 'Fail2Ban/%s' % version
self.assertEqual(len(act), 4)
self.assertEqual(len(act), 2)
self.assertEqual(act[0], ['set', 'blocklisttest', 'action', 'blocklist_de', 'agent', useragent])
self.assertEqual(act[1], ['set', 'blocklisttest', 'action', 'badips', 'agent', useragent])
self.assertEqual(eval(act[2][5]).get('agent', '<wrong>'), useragent)
self.assertEqual(act[3], ['set', 'blocklisttest', 'action', 'mynetwatchman', 'agent', useragent])
self.assertEqual(act[1], ['set', 'blocklisttest', 'action', 'mynetwatchman', 'agent', useragent])
@with_tmpdir
def testGlob(self, d):
@@ -752,9 +751,9 @@ class JailsReaderTest(LogCaptureTestCase):
['add', 'tz_correct', 'auto'],
['start', 'tz_correct'],
['config-error',
"Jail 'brokenactiondef' skipped, because of wrong configuration: Invalid action definition 'joho[foo'"],
"Jail 'brokenactiondef' skipped, because of wrong configuration: Invalid action definition 'joho[foo': unexpected option syntax"],
['config-error',
"Jail 'brokenfilterdef' skipped, because of wrong configuration: Invalid filter definition 'flt[test'"],
"Jail 'brokenfilterdef' skipped, because of wrong configuration: Invalid filter definition 'flt[test': unexpected option syntax"],
['config-error',
"Jail 'missingaction' skipped, because of wrong configuration: Unable to read action 'noactionfileforthisaction'"],
['config-error',
@@ -975,6 +974,7 @@ class JailsReaderTest(LogCaptureTestCase):
['set', 'syslogsocket', 'auto'],
['set', 'loglevel', "INFO"],
['set', 'logtarget', '/var/log/fail2ban.log'],
['set', 'allowipv6', 'auto'],
['set', 'dbfile', '/var/lib/fail2ban/fail2ban.sqlite3'],
['set', 'dbmaxmatches', 10],
['set', 'dbpurgeage', '1d'],
+10 -5
View File
@@ -29,7 +29,7 @@ import tempfile
import sqlite3
import shutil
from ..server.filter import FileContainer
from ..server.filter import FileContainer, Filter
from ..server.mytime import MyTime
from ..server.ticket import FailTicket
from ..server.actions import Actions, Utils
@@ -212,19 +212,20 @@ class DatabaseTest(LogCaptureTestCase):
self.jail.name in self.db.getJailNames(True),
"Jail not added to database")
def testAddLog(self):
def _testAddLog(self):
self.testAddJail() # Jail required
_, filename = tempfile.mkstemp(".log", "Fail2BanDb_")
self.fileContainer = FileContainer(filename, "utf-8")
self.db.addLog(self.jail, self.fileContainer)
pos = self.db.addLog(self.jail, self.fileContainer)
self.assertTrue(pos is None); # unknown previously
self.assertIn(filename, self.db.getLogPaths(self.jail))
os.remove(filename)
def testUpdateLog(self):
self.testAddLog() # Add log file
self._testAddLog() # Add log file
# Write some text
filename = self.fileContainer.getFileName()
@@ -544,17 +545,21 @@ class DatabaseTest(LogCaptureTestCase):
self.testAddJail() # Jail required
self.jail.database = self.db
self.db.addJail(self.jail)
actions = Actions(self.jail)
actions = self.jail.actions
actions.add(
"action_checkainfo",
os.path.join(TEST_FILES_DIR, "action.d/action_checkainfo.py"),
{})
actions.banManager.setBanTotal(20)
self.jail._Jail__filter = flt = Filter(self.jail)
flt.failManager.setFailTotal(50)
ticket = FailTicket("1.2.3.4")
ticket.setAttempt(5)
ticket.setMatches(['test', 'test'])
self.jail.putFailTicket(ticket)
actions._Actions__checkBan()
self.assertLogged("ban ainfo %s, %s, %s, %s" % (True, True, True, True))
self.assertLogged("jail info %d, %d, %d, %d" % (1, 21, 0, 50))
def testDelAndAddJail(self):
self.testAddJail() # Add jail
+6
View File
@@ -516,6 +516,9 @@ class CustomDateFormatsTest(unittest.TestCase):
(1072746123.0 - 3600, "{^LN-BEG}%ExY-%Exm-%Exd %ExH:%ExM:%ExS(?: %Z)?", "[2003-12-30 01:02:03] server ..."),
(1072746123.0, "{^LN-BEG}%ExY-%Exm-%Exd %ExH:%ExM:%ExS(?: %z)?", "[2003-12-30 01:02:03 UTC] server ..."),
(1072746123.0, "{^LN-BEG}%ExY-%Exm-%Exd %ExH:%ExM:%ExS(?: %Z)?", "[2003-12-30 01:02:03 UTC] server ..."),
(1072746123.0, "{^LN-BEG}%ExY-%Exm-%Exd %ExH:%ExM:%ExS(?: %z)?", "[2003-12-30 01:02:03 Z] server ..."),
(1072746123.0, "{^LN-BEG}%ExY-%Exm-%Exd %ExH:%ExM:%ExS(?: %z)?", "[2003-12-30 01:02:03 +0000] server ..."),
(1072746123.0, "{^LN-BEG}%ExY-%Exm-%Exd %ExH:%ExM:%ExS(?: %Z)?", "[2003-12-30 01:02:03 Z] server ..."),
):
logSys.debug('== test: %r', (matched, dp, line))
if dp is None:
@@ -551,6 +554,9 @@ class CustomDateFormatsTest(unittest.TestCase):
(1123970401.0, "^%ExH:%ExM:%ExS**", '00:00:01'),
# cover date with current year, in test cases now == Aug 2005 -> back to last year (Sep 2004):
(1094068799.0, "^%m/%d %ExH:%ExM:%ExS**", '09/01 21:59:59'),
# no time (only date) in pattern, assume local 00:00:00 for H:M:S :
(1093989600.0, "^%Y-%m-%d**", '2004-09-01'),
(1093996800.0, "^%Y-%m-%d%z**", '2004-09-01Z'),
):
logSys.debug('== test: %r', (matched, dp, line))
dd = DateDetector()
+17 -16
View File
@@ -230,7 +230,7 @@ def _start_params(tmp, use_stock=False, use_stock_cfg=None,
os.symlink(os.path.abspath(pjoin(STOCK_CONF_DIR, n)), pjoin(cfg, n))
if create_before_start:
for n in create_before_start:
_write_file(n % {'tmp': tmp}, 'w', '')
_write_file(n % {'tmp': tmp}, 'w')
# parameters (sock/pid and config, increase verbosity, set log, etc.):
vvv, llev = (), "INFO"
if unittest.F2B.log_level < logging.INFO: # pragma: no cover
@@ -937,10 +937,8 @@ class Fail2banServerTest(Fail2banClientServerBase):
"Jail 'broken-jail' skipped, because of wrong configuration", all=True)
# enable both jails, 3 logs for jail1, etc...
# truncate test-log - we should not find unban/ban again by reload:
self.pruneLog("[test-phase 1b]")
_write_jail_cfg(actions=[1,2])
_write_file(test1log, "w+")
if unittest.F2B.log_level < logging.DEBUG: # pragma: no cover
_out_file(test1log)
self.execCmd(SUCCESS, startparams, "reload")
@@ -1003,7 +1001,7 @@ class Fail2banServerTest(Fail2banClientServerBase):
self.pruneLog("[test-phase 2b]")
# write new failures:
_write_file(test2log, "w+", *(
_write_file(test2log, "a+", *(
(str(int(MyTime.time())) + " error 403 from 192.0.2.2: test 2",) * 3 +
(str(int(MyTime.time())) + " error 403 from 192.0.2.3: test 2",) * 3 +
(str(int(MyTime.time())) + " failure 401 from 192.0.2.4: test 2",) * 3 +
@@ -1062,10 +1060,6 @@ class Fail2banServerTest(Fail2banClientServerBase):
self.assertEqual(self.execCmdDirect(startparams,
'get', 'test-jail1', 'banned', '192.0.2.3', '192.0.2.9')[1], [1, 0])
# rotate logs:
_write_file(test1log, "w+")
_write_file(test2log, "w+")
# restart jail without unban all:
self.pruneLog("[test-phase 2c]")
self.execCmd(SUCCESS, startparams,
@@ -1183,7 +1177,7 @@ class Fail2banServerTest(Fail2banClientServerBase):
# now write failures again and check already banned (jail1 was alive the whole time) and new bans occurred (jail1 was alive the whole time):
self.pruneLog("[test-phase 5]")
_write_file(test1log, "w+", *(
_write_file(test1log, "a+", *(
(str(int(MyTime.time())) + " failure 401 from 192.0.2.1: test 5",) * 3 +
(str(int(MyTime.time())) + " error 403 from 192.0.2.5: test 5",) * 3 +
(str(int(MyTime.time())) + " failure 401 from 192.0.2.6: test 5",) * 3
@@ -1326,7 +1320,7 @@ class Fail2banServerTest(Fail2banClientServerBase):
'backend = polling',
'usedns = no',
'logpath = %(tmp)s/blck-failures.log',
'action = nginx-block-map[blck_lst_reload="", blck_lst_file="%(tmp)s/blck-lst.map"]',
'action = nginx-block-map[srv_cmd="echo nginx", srv_pid="%(tmp)s/f2b.pid", blck_lst_file="%(tmp)s/blck-lst.map"]',
' blocklist_de[actionban=\'curl() { echo "*** curl" "$*";}; <Definition/actionban>\', email="Fail2Ban <fail2ban@localhost>", '
'apikey="TEST-API-KEY", agent="fail2ban-test-agent", service=<name>]',
'filter =',
@@ -1366,6 +1360,8 @@ class Fail2banServerTest(Fail2banClientServerBase):
self.assertIn('\\125-000-004 1;\n', mp)
self.assertIn('\\125-000-005 1;\n', mp)
# check nginx reload is logged (pid of fail2ban is used to simulate success check nginx is running):
self.assertLogged("stdout: 'nginx -qt'", "stdout: 'nginx -s reload'", all=True)
# check blocklist_de substitution (e. g. new-line after <matches>):
self.assertLogged(
"stdout: '*** curl --fail --data-urlencode server=Fail2Ban <fail2ban@localhost>"
@@ -1408,8 +1404,9 @@ class Fail2banServerTest(Fail2banClientServerBase):
'jails': (
# default:
'''test_action = dummy[actionstart_on_demand=1, init="start: %(__name__)s", target="%(tmp)s/test.txt",
actionban='<known/actionban>;
echo "<matches>"; printf "=====\\n%%b\\n=====\\n\\n" "<matches>" >> <target>']''',
actionban='<known/actionban>; echo "found: <jail.found> / <jail.found_total>, banned: <jail.banned> / <jail.banned_total>"
echo "<matches>"; printf "=====\\n%%b\\n=====\\n\\n" "<matches>" >> <target>',
actionstop='<known/actionstop>; echo "stats <name> - found: <jail.found_total>, banned: <jail.banned_total>"']''',
# jail sendmail-auth:
'[sendmail-auth]',
'backend = polling',
@@ -1454,7 +1451,8 @@ class Fail2banServerTest(Fail2banClientServerBase):
_write_file(lgfn, "w+", *smaut_msg)
# wait and check it caused banned (and dump in the test-file):
self.assertLogged(
"[sendmail-auth] Ban 192.0.2.1", "1 ticket(s) in 'sendmail-auth'", all=True, wait=MID_WAITTIME)
"[sendmail-auth] Ban 192.0.2.1", "stdout: 'found: 0 / 3, banned: 1 / 1'",
"1 ticket(s) in 'sendmail-auth'", all=True, wait=MID_WAITTIME)
_out_file(tofn)
td = _read_file(tofn)
# check matches (maxmatches = 2, so only 2 & 3 available):
@@ -1465,10 +1463,11 @@ class Fail2banServerTest(Fail2banClientServerBase):
self.pruneLog("[test-phase sendmail-reject]")
# write log:
_write_file(lgfn, "w+", *smrej_msg)
_write_file(lgfn, "a+", *smrej_msg)
# wait and check it caused banned (and dump in the test-file):
self.assertLogged(
"[sendmail-reject] Ban 192.0.2.2", "1 ticket(s) in 'sendmail-reject'", all=True, wait=MID_WAITTIME)
"[sendmail-reject] Ban 192.0.2.2", "stdout: 'found: 0 / 3, banned: 1 / 1'",
"1 ticket(s) in 'sendmail-reject'", all=True, wait=MID_WAITTIME)
_out_file(tofn)
td = _read_file(tofn)
# check matches (no maxmatches, so all matched messages are available):
@@ -1482,6 +1481,8 @@ class Fail2banServerTest(Fail2banClientServerBase):
# wait a bit:
self.assertLogged(
"Reload finished.",
"stdout: 'stats sendmail-auth - found: 3, banned: 1'",
"stdout: 'stats sendmail-reject - found: 3, banned: 1'",
"[sendmail-auth] Restore Ban 192.0.2.1", "1 ticket(s) in 'sendmail-auth'", all=True, wait=MID_WAITTIME)
# check matches again - (dbmaxmatches = 1), so it should be only last match after restart:
td = _read_file(tofn)
@@ -1590,7 +1591,7 @@ class Fail2banServerTest(Fail2banClientServerBase):
wakeObs = False
_observer_wait_before_incrban(lambda: wakeObs)
# write again (IP already bad):
_write_file(test1log, "w+", *(
_write_file(test1log, "a+", *(
(str(int(MyTime.time())) + " failure 401 from 192.0.2.11: I'm very bad \"hacker\" `` $(echo test)",) * 2
))
# wait for ban:
+54 -8
View File
@@ -25,6 +25,7 @@ __license__ = "GPL"
import os
import sys
import tempfile
import unittest
from ..client import fail2banregex
@@ -80,6 +81,11 @@ def _test_exec_command_line(*args):
sys.stderr = _org['stderr']
return _exit_code
def _reset():
# reset global warn-counter:
from ..server.filter import _decode_line_warn
_decode_line_warn.clear()
STR_00 = "Dec 31 11:59:59 [sshd] error: PAM: Authentication failure for kevin from 192.0.2.0"
STR_00_NODT = "[sshd] error: PAM: Authentication failure for kevin from 192.0.2.0"
@@ -122,6 +128,7 @@ class Fail2banRegexTest(LogCaptureTestCase):
"""Call before every test case."""
LogCaptureTestCase.setUp(self)
setUpMyTime()
_reset()
def tearDown(self):
"""Call after every test case."""
@@ -141,6 +148,12 @@ class Fail2banRegexTest(LogCaptureTestCase):
))
self.assertLogged("Unable to compile regular expression")
def testWrongFilterOptions(self):
self.assertFalse(_test_exec(
"test", "flt[a='x,y,z',b=z,y,x]"
))
self.assertLogged("Wrong filter name or options", "wrong syntax at 14: y,x", all=True)
def testDirectFound(self):
self.assertTrue(_test_exec(
"--datepattern", r"^(?:%a )?%b %d %H:%M:%S(?:\.%f)?(?: %ExY)?",
@@ -395,7 +408,17 @@ class Fail2banRegexTest(LogCaptureTestCase):
"Found a match but no valid date/time found",
"Match without a timestamp:", all=True)
self.pruneLog()
def testIncompleteDateTime(self):
# datepattern in followed lines doesn't match previously known pattern + line is too short
# (logging break-off, no flush, etc):
self.assertTrue(_test_exec(
'-o', 'Found-ADDR:<ip>',
'192.0.2.1 - - [02/May/2021:18:40:55 +0100] "GET / HTTP/1.1" 302 328 "-" "Mozilla/5.0" "-"\n'
'192.0.2.2 - - [02/May/2021:18:40:55 +0100\n'
'192.0.2.3 - - [02/May/2021:18:40:55',
'^<ADDR>'))
self.assertLogged(
"Found-ADDR:192.0.2.1", "Found-ADDR:192.0.2.2", "Found-ADDR:192.0.2.3", all=True)
def testFrmtOutputWrapML(self):
unittest.F2B.SkipIfCfgMissing(stock=True)
@@ -448,14 +471,8 @@ class Fail2banRegexTest(LogCaptureTestCase):
FILENAME_ZZZ_GEN, FILENAME_ZZZ_GEN
))
def _reset(self):
# reset global warn-counter:
from ..server.filter import _decode_line_warn
_decode_line_warn.clear()
def testWronChar(self):
unittest.F2B.SkipIfCfgMissing(stock=True)
self._reset()
self.assertTrue(_test_exec(
"-l", "notice", # put down log-level, because of too many debug-messages
"--datepattern", r"^(?:%a )?%b %d %H:%M:%S(?:\.%f)?(?: %ExY)?",
@@ -471,7 +488,6 @@ class Fail2banRegexTest(LogCaptureTestCase):
def testWronCharDebuggex(self):
unittest.F2B.SkipIfCfgMissing(stock=True)
self._reset()
self.assertTrue(_test_exec(
"-l", "notice", # put down log-level, because of too many debug-messages
"--datepattern", r"^(?:%a )?%b %d %H:%M:%S(?:\.%f)?(?: %ExY)?",
@@ -484,6 +500,36 @@ class Fail2banRegexTest(LogCaptureTestCase):
self.assertLogged('https://')
def testNLCharAsPartOfUniChar(self):
fname = tempfile.mktemp(prefix='tmp_fail2ban', suffix='uni')
# test two multi-byte encodings (both contains `\x0A` in either \x02\x0A or \x0A\x02):
for enc in ('utf-16be', 'utf-16le'):
self.pruneLog("[test-phase encoding=%s]" % enc)
try:
fout = open(fname, 'wb')
# test on unicode string containing \x0A as part of uni-char,
# it must produce exactly 2 lines (both are failures):
for l in (
u'1490349000 \u20AC Failed auth: invalid user Test\u020A from 192.0.2.1\n',
u'1490349000 \u20AC Failed auth: invalid user TestI from 192.0.2.2\n'
):
fout.write(l.encode(enc))
fout.close()
self.assertTrue(_test_exec(
"-l", "notice", # put down log-level, because of too many debug-messages
"--encoding", enc,
"--datepattern", r"^EPOCH",
fname, r"Failed .* from <HOST>",
))
self.assertLogged(" encoding : %s" % enc,
"Lines: 2 lines, 0 ignored, 2 matched, 0 missed", all=True)
self.assertNotLogged("Missed line(s)")
finally:
fout.close()
os.unlink(fname)
def testExecCmdLine_Usage(self):
self.assertNotEqual(_test_exec_command_line(), 0)
self.pruneLog()
@@ -8,6 +8,9 @@ class TestAction(ActionBase):
self._logSys.info("ban ainfo %s, %s, %s, %s",
aInfo["ipmatches"] != '', aInfo["ipjailmatches"] != '', aInfo["ipfailures"] > 0, aInfo["ipjailfailures"] > 0
)
self._logSys.info("jail info %d, %d, %d, %d",
aInfo["jail.banned"], aInfo["jail.banned_total"], aInfo["jail.found"], aInfo["jail.found_total"]
)
def unban(self, aInfo):
pass
@@ -3,6 +3,8 @@
[Tue Mar 16 15:39:29 2010] [error] [client 58.179.109.179] Invalid URI in request \xf9h\xa9\xf3\x88\x8cXKj \xbf-l*4\x87n\xe4\xfe\xd4\x1d\x06\x8c\xf8m\\rS\xf6n\xeb\x8
# failJSON: { "time": "2010-03-15T15:44:47", "match": true , "host": "121.222.2.133" }
[Mon Mar 15 15:44:47 2010] [error] [client 121.222.2.133] Invalid URI in request n\xed*\xbe*\xab\xefd\x80\xb5\xae\xf6\x01\x10M?\xf2\xce\x13\x9c\xd7\xa0N\xa7\xdb%0\xde\xe0\xfc\xd2\xa0\xfe\xe9w\xee\xc4`v\x9b[{\x0c:\xcb\x93\xc6\xa0\x93\x9c`l\\\x8d\xc9
# failJSON: { "time": "2010-03-15T16:04:06", "match": true , "host": "192.0.2.1", "desc": "AH00126 failure, gh-2908" }
[Sat Mar 15 16:04:06.105212 2010] [core:error] [pid 17408] [client 192.0.2.1:55280] AH00126: Invalid URI in request GET /static/../../../a/../../../../etc/passwd HTTP/1.1
# http://forum.nconf.org/viewtopic.php?f=14&t=427&p=1488
# failJSON: { "time": "2010-07-30T11:23:54", "match": true , "host": "10.85.6.69" }
+2
View File
@@ -19,6 +19,8 @@
[2012-02-13 17:44:26] NOTICE[1638] chan_iax2.c: Host 1.2.3.4 failed MD5 authentication for 'Fail2ban' (e7df7cd2ca07f4f1ab415d457a6e1c13 != 53ac4bc41ee4ec77888ed4aa50677247)
# failJSON: { "time": "2013-02-05T23:44:42", "match": true , "host": "1.2.3.4" }
[2013-02-05 23:44:42] NOTICE[436][C-00000fa9] chan_sip.c: Call from '' (1.2.3.4:10836) to extension '0972598285108' rejected because extension not found in context 'default'.
# failJSON: { "time": "2005-01-18T17:39:50", "match": true , "host": "1.2.3.4" }
[Jan 18 17:39:50] NOTICE[12049]: res_pjsip_session.c:2337 new_invite: Call from 'anonymous' (TCP:[1.2.3.4]:61470) to extension '9011+442037690237' rejected because extension not found in context 'default'.
# failJSON: { "time": "2013-03-26T15:47:54", "match": true , "host": "1.2.3.4" }
[2013-03-26 15:47:54] NOTICE[1237] chan_sip.c: Registration from '"100"sip:100@1.2.3.4' failed for '1.2.3.4:23930' - No matching peer found
# failJSON: { "time": "2013-05-13T07:10:53", "match": true , "host": "1.2.3.4" }
+8
View File
@@ -34,6 +34,9 @@ Jul 02 13:49:32 hostname dovecot[442]: dovecot: auth(default): pam(account@MYSER
# failJSON: { "time": "2005-01-29T05:32:50", "match": true , "host": "1.2.3.4" }
Jan 29 05:32:50 mail dovecot: auth-worker(304): pam(username,1.2.3.4): pam_authenticate() failed: Authentication failure (password mismatch?)
# failJSON: { "time": "2005-01-29T18:55:55", "match": true , "host": "192.0.2.4", "desc": "Password mismatch (title case, gh-2880)" }
Jan 29 18:55:55 mail dovecot: auth-worker(12182): pam(user,192.0.2.4): pam_authenticate() failed: Authentication failure (Password mismatch?)
# failJSON: { "time": "2005-01-29T05:13:40", "match": true , "host": "1.2.3.4" }
Jan 29 05:13:40 mail dovecot: auth-worker(31326): pam(username,1.2.3.4): unknown user
@@ -52,6 +55,11 @@ Jun 11 13:57:17 main dovecot: auth: sql(admin@example.ru,192.168.144.226,<6rXunF
#failJSON: { "time": "2005-06-11T13:57:17", "match": true, "host": "192.168.178.25", "desc": "allow more verbose logging, gh-2573" }
Jun 11 13:57:17 main dovecot: auth: ldap(user@server.org,192.168.178.25,<LZmGp6mZaMrAqLIZ>): Password mismatch (for LDAP bind) (SHA1 of given password: f638ff)
# failJSON: { "time": "2005-06-12T11:48:12", "match": true , "host": "192.0.2.6" }
Jun 12 11:48:12 auth-worker(80180): Info: conn unix:auth-worker (uid=143): auth-worker<13247>: sql(support,192.0.2.6): unknown user
# failJSON: { "time": "2005-06-12T23:06:05", "match": true , "host": "192.0.2.7" }
Jun 12 23:06:05 auth-worker(57065): Info: conn unix:auth-worker (uid=143): auth-worker<225622>: sql(user@domain.com,192.0.2.7,<Yx7+W8+Io>): Password mismatch
# failJSON: { "time": "2005-01-29T14:38:51", "match": true , "host": "192.0.2.6", "desc": "PAM Permission denied (gh-1897)" }
Jan 29 14:38:51 example.com dovecot[24941]: auth-worker(30165): pam(user@example.com,192.0.2.6,<PNHQq8pZhqIKAQGd>): pam_authenticate() failed: Permission denied
+11 -1
View File
@@ -3,5 +3,15 @@ Apr 26 13:15:25 webserver example.com: https://example.com|1430068525|user|1.2.3
# failJSON: { "time": "2005-04-26T13:15:25", "match": true , "host": "1.2.3.4" }
Apr 26 13:15:25 webserver example.com: https://example.com/subdir|1430068525|user|1.2.3.4|https://example.com/subdir/user|https://example.com/subdir/user|0||Login attempt failed for drupaladmin.
# failJSON: { "time": "2005-04-26T13:19:08", "match": false , "host": "1.2.3.4" }
# failJSON: { "time": "2005-04-26T13:19:08", "match": false , "host": "1.2.3.4", "user": "drupaladmin" }
Apr 26 13:19:08 webserver example.com: https://example.com|1430068748|user|1.2.3.4|https://example.com/user|https://example.com/user|1||Session opened for drupaladmin.
# failJSON: { "time": "2005-04-26T13:20:00", "match": false, "desc": "attempt to inject on URI (pipe, login failed for), not a failure, gh-2742" }
Apr 26 13:20:00 host drupal-site: https://example.com|1613063581|user|192.0.2.5|https://example.com/user/login?test=%7C&test2=%7C...|https://example.com/user/login?test=|&test2=|0||Login attempt failed for tester|2||Session revisited for drupaladmin.
# failJSON: { "time": "2005-04-26T13:20:01", "match": true , "host": "192.0.2.7", "user": "Jack Sparrow", "desc": "log-format change - for -> from, user name with space, gh-2742" }
Apr 26 13:20:01 mweb drupal_site[24864]: https://www.example.com|1613058599|user|192.0.2.7|https://www.example.com/en/user/login|https://www.example.com/en/user/login|0||Login attempt failed from Jack Sparrow.
# failJSON: { "time": "2005-04-26T13:20:02", "match": true , "host": "192.0.2.4", "desc": "attempt to inject on URI (pipe), login failed, gh-2742" }
Apr 26 13:20:02 host drupal-site: https://example.com|1613063581|user|192.0.2.4|https://example.com/user/login?test=%7C&test2=%7C|https://example.com/user/login?test=|&test2=||0||Login attempt failed from 192.0.2.4.
# failJSON: { "time": "2005-04-26T13:20:03", "match": false, "desc": "attempt to inject on URI (pipe, login failed from), not a failure, gh-2742" }
Apr 26 13:20:03 host drupal-site: https://example.com|1613063581|user|192.0.2.5|https://example.com/user/login?test=%7C&test2=%7C...|https://example.com/user/login?test=|&test2=|0||Login attempt failed from 1.2.3.4|2||Session revisited for drupaladmin.
+3
View File
@@ -43,6 +43,9 @@
# failJSON: { "time": "2014-01-12T02:07:48", "match": true , "host": "85.214.85.40" }
2014-01-12 02:07:48 dovecot_login authenticator failed for h1832461.stratoserver.net (User) [85.214.85.40]: 535 Incorrect authentication data (set_id=scanner)
# failJSON: { "time": "2019-10-22T03:39:17", "match": true , "host": "192.0.2.37", "desc": "pid-prefix in form of 'mx1 exim[...]:', gh-2553" }
2019-10-22 03:39:17 mx1 exim[29786]: dovecot_login authenticator failed for (User) [192.0.2.37]: 535 Incorrect authentication data (set_id=test@domain.com)
# failJSON: { "time": "2014-12-02T03:00:23", "match": true , "host": "193.254.202.35" }
2014-12-02 03:00:23 auth_plain authenticator failed for (rom182) [193.254.202.35]:41556 I=[10.0.0.1]:25: 535 Incorrect authentication data (set_id=webmaster)
+6 -1
View File
@@ -1,4 +1,3 @@
#authentification failure (mod_auth)
# failJSON: { "time": "2011-12-25T17:09:20", "match": true , "host": "4.4.4.4" }
2011-12-25 17:09:20: (http_auth.c.875) password doesn't match for /gitweb/ username: francois, IP: 4.4.4.4
# failJSON: { "time": "2012-09-26T10:24:35", "match": true , "host": "4.4.4.4" }
@@ -7,3 +6,9 @@
2013-08-25 00:24:55: (http_auth.c.877) get_password failed, IP: 4.4.4.4
# failJSON: { "time": "2018-01-16T14:10:32", "match": true , "host": "192.0.2.1", "desc": "http_auth -> mod_auth, gh-2018" }
2018-01-16 14:10:32: (mod_auth.c.525) password doesn't match for /test-url username: test, IP: 192.0.2.1
# failJSON: { "time": "2021-09-30T16:05:33", "match": true , "host": "192.0.2.2", "user":"test", "desc": "gh-3116" }
2021-09-30 16:05:33: mod_auth.c.828) password doesn't match for /secure/ username: test IP: 192.0.2.2
# failJSON: { "time": "2021-09-30T17:44:37", "match": true , "host": "192.0.2.3", "user":"tester", "desc": "gh-3116" }
2021-09-30 17:44:37: (mod_auth.c.791) digest: auth failed for tester : wrong password, IP: 192.0.2.3
# failJSON: { "time": "2021-09-30T17:44:37", "match": true , "host": "192.0.2.4", "desc": "gh-3116" }
2021-09-30 17:44:37: (mod_auth.c.791) digest: auth failed: uri mismatch (/uri1 != /uri2), IP: 192.0.2.4
+2 -2
View File
@@ -1,7 +1,7 @@
# Previous version --
# failJSON: { "time": "2005-04-16T21:05:29", "match": true , "host": "69.93.127.111" }
# failJSON: { "time": "2005-04-17T06:05:29", "match": true , "host": "69.93.127.111" }
[PDT Apr 16 21:05:29] error : Warning: Client '69.93.127.111' supplied unknown user 'foo' accessing monit httpd
# failJSON: { "time": "2005-04-16T20:59:33", "match": true , "host": "97.113.189.111" }
# failJSON: { "time": "2005-04-17T05:59:33", "match": true , "host": "97.113.189.111" }
[PDT Apr 16 20:59:33] error : Warning: Client '97.113.189.111' supplied wrong password for user 'admin' accessing monit httpd
# Current version -- corresponding "https://bitbucket.org/tildeslash/monit/src/6905335aa903d425cae732cab766bd88ea5f2d1d/src/http/processor.c?at=master&fileviewer=file-view-default#processor.c-728"
+8
View File
@@ -0,0 +1,8 @@
# failJSON: { "time": "2021-04-14T08:11:01", "match": false, "desc": "should be ignored: successful request" }
Wed Apr 14 08:11:01 2021 - OK - [127.0.0.1] "GET /monitorix-cgi/monitorix.cgi - Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0"
# failJSON: { "time": "2021-04-14T08:54:22", "match": true, "host": "127.0.0.1", "desc": "file does not exist" }
Wed Apr 14 08:54:22 2021 - NOTEXIST - [127.0.0.1] File does not exist: /manager/html
# failJSON: { "time": "2021-04-14T11:24:31", "match": true, "host": "127.0.0.1", "desc": "access not allowed" }
Wed Apr 14 11:24:31 2021 - NOTALLOWED - [127.0.0.1] Access not allowed: /monitorix/
# failJSON: { "time": "2021-04-14T11:26:08", "match": true, "host": "127.0.0.1", "desc": "authentication error" }
Wed Apr 14 11:26:08 2021 - AUTHERR - [127.0.0.1] Authentication error: /monitorix/
+11
View File
@@ -0,0 +1,11 @@
# failJSON: { "time": "2020-02-24T16:05:21", "match": true , "host": "192.0.2.1" }
2020-02-24 16:05:21.00 Logon Login failed for user 'Backend'. Reason: Could not find a login matching the name provided. [CLIENT: 192.0.2.1]
# failJSON: { "time": "2020-02-24T16:30:25", "match": true , "host": "192.0.2.2" }
2020-02-24 16:30:25.88 Logon Login failed for user '===)jf02hüas9ä##22f'. Reason: Could not find a login matching the name provided. [CLIENT: 192.0.2.2]
# failJSON: { "time": "2020-02-24T16:31:12", "match": true , "host": "192.0.2.3" }
2020-02-24 16:31:12.20 Logon Login failed for user ''. Reason: An attempt to login using SQL authentication failed. Server is configured for Integrated authentication only. [CLIENT: 192.0.2.3]
# failJSON: { "time": "2020-02-24T16:31:26", "match": true , "host": "192.0.2.4", "user":"O'Leary" }
2020-02-24 16:31:26.01 Logon Login failed for user 'O'Leary'. Reason: Could not find a login matching the name provided. [CLIENT: 192.0.2.4]
# failJSON: { "time": "2020-02-24T16:31:26", "match": false, "desc": "test injection in possibly unescaped foreign input" }
2020-02-24 16:31:26.02 Wrong data received: Logon Login failed for user 'test'. Reason: Could not find a login matching the name provided. [CLIENT: 192.0.2.5]
+5
View File
@@ -26,3 +26,8 @@ Aug 27 16:58:31 vhost1-ua named[29206]: client 176.9.92.38#42592 (simmarket.com.
# failJSON: { "time": "2004-08-27T16:59:00", "match": true , "host": "192.0.2.1", "desc": "new log format, 9.11.0 (#2406)" }
Aug 27 16:59:00 host named[28098]: client @0x7f6450002ef0 192.0.2.1#23332 (example.com): bad zone transfer request: 'test.com/IN': non-authoritative zone (NOTAUTH)
# filterOptions: {"logtype": "journal"}
# failJSON: { "match": true , "host": "192.0.2.1", "desc": "systemd-journal entry" }
atom named[1806]: client @0x7fb13400eec0 192.0.2.1#61977 (.): query (cache) './ANY/IN' denied
@@ -0,0 +1,23 @@
# failJSON: { "time": "2015-01-20T19:53:28", "match": true , "host": "12.34.56.78" }
12.34.56.78 - - [20/Jan/2015:19:53:28 +0100] "" 400 47 "-" "-" "-"
# failJSON: { "time": "2015-01-20T19:53:28", "match": true , "host": "12.34.56.78" }
12.34.56.78 - root [20/Jan/2015:19:53:28 +0100] "" 400 47 "-" "-" "-"
# failJSON: { "time": "2015-01-20T19:53:28", "match": true , "host": "12.34.56.78" }
12.34.56.78 - - [20/Jan/2015:19:53:28 +0100] "\x03\x00\x00/*\xE0\x00\x00\x00\x00\x00Cookie: mstshash=Administr" 400 47 "-" "-" "-"
# failJSON: { "time": "2015-01-20T19:53:28", "match": true , "host": "12.34.56.78" }
12.34.56.78 - - [20/Jan/2015:19:53:28 +0100] "GET //admin/pma/scripts/setup.php HTTP/1.1" 400 47 "-" "-" "-"
# failJSON: { "time": "2015-01-20T19:54:28", "match": true , "host": "12.34.56.78" }
12.34.56.78 - - [20/Jan/2015:19:54:28 +0100] "HELP" 400 47 "-" "-" "-"
# failJSON: { "time": "2015-01-20T19:55:28", "match": true , "host": "12.34.56.78" }
12.34.56.78 - - [20/Jan/2015:19:55:28 +0100] "batman" 400 47 "-" "-" "-"
# failJSON: { "time": "2015-01-20T01:17:07", "match": true , "host": "7.8.9.10" }
7.8.9.10 - root [20/Jan/2015:01:17:07 +0100] "CONNECT 123.123.123.123 HTTP/1.1" 400 162 "-" "-" "-"
# failJSON: { "time": "2014-12-12T22:59:02", "match": true , "host": "2.5.2.5" }
2.5.2.5 - tomcat [12/Dec/2014:22:59:02 +0100] "GET /cgi-bin/tools/tools.pl HTTP/1.1" 400 162 "-" "-" "-"

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