From 33598452420f1b6372c60c3d418cf9dfbeacaa41 Mon Sep 17 00:00:00 2001 From: Arto Jantunen Date: Wed, 2 Oct 2019 19:18:58 +0300 Subject: [PATCH 01/87] Stop setting PIDFile, useless when Type=simple This has been unneeded since commit 528a7a5ab which converted this from Type=forking to Type=simple. --- files/fail2ban.service.in | 1 - 1 file changed, 1 deletion(-) diff --git a/files/fail2ban.service.in b/files/fail2ban.service.in index 9a245c61..98b0fc11 100644 --- a/files/fail2ban.service.in +++ b/files/fail2ban.service.in @@ -13,7 +13,6 @@ ExecStart=@BINDIR@/fail2ban-server -xf start # ExecStart=@BINDIR@/fail2ban-server -xf --logtarget=sysout start ExecStop=@BINDIR@/fail2ban-client stop ExecReload=@BINDIR@/fail2ban-client reload -PIDFile=/run/fail2ban/fail2ban.pid Restart=on-failure RestartPreventExitStatus=0 255 From 60b136333e69b1fc042200df1c5e63022c7dda41 Mon Sep 17 00:00:00 2001 From: Arto Jantunen Date: Wed, 2 Oct 2019 18:21:09 +0300 Subject: [PATCH 02/87] Use RuntimeDirectory to create /run/fail2ban Instead of the duplicated tmpfiles + ExecStartPre. This way the lifetime of that directory becomes fixed, and also User is automatically respected for the ownership of the directory (making it easy to run fail2ban as a non-root user, like it was with at least the Debian init script). --- files/fail2ban-tmpfiles.conf | 1 - files/fail2ban.service.in | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 files/fail2ban-tmpfiles.conf diff --git a/files/fail2ban-tmpfiles.conf b/files/fail2ban-tmpfiles.conf deleted file mode 100644 index 68f8e345..00000000 --- a/files/fail2ban-tmpfiles.conf +++ /dev/null @@ -1 +0,0 @@ -D /run/fail2ban 0755 root root - \ No newline at end of file diff --git a/files/fail2ban.service.in b/files/fail2ban.service.in index 98b0fc11..ddaa41b4 100644 --- a/files/fail2ban.service.in +++ b/files/fail2ban.service.in @@ -7,7 +7,7 @@ PartOf=iptables.service firewalld.service ip6tables.service ipset.service nftabl [Service] Type=simple Environment="PYTHONNOUSERSITE=1" -ExecStartPre=/bin/mkdir -p /run/fail2ban +RuntimeDirectory=fail2ban ExecStart=@BINDIR@/fail2ban-server -xf start # if should be logged in systemd journal, use following line or set logtarget to sysout in fail2ban.local # ExecStart=@BINDIR@/fail2ban-server -xf --logtarget=sysout start From f376da4bec5742097b27feb4171aca9dfe1af1cb Mon Sep 17 00:00:00 2001 From: Arto Jantunen Date: Fri, 4 Oct 2019 18:08:47 +0300 Subject: [PATCH 03/87] Set StateDirectory This automatically handles permissions for /var/lib/fail2ban when run as a non-root user. --- files/fail2ban.service.in | 1 + 1 file changed, 1 insertion(+) diff --git a/files/fail2ban.service.in b/files/fail2ban.service.in index ddaa41b4..e93fd596 100644 --- a/files/fail2ban.service.in +++ b/files/fail2ban.service.in @@ -8,6 +8,7 @@ PartOf=iptables.service firewalld.service ip6tables.service ipset.service nftabl Type=simple Environment="PYTHONNOUSERSITE=1" RuntimeDirectory=fail2ban +StateDirectory=fail2ban ExecStart=@BINDIR@/fail2ban-server -xf start # if should be logged in systemd journal, use following line or set logtarget to sysout in fail2ban.local # ExecStart=@BINDIR@/fail2ban-server -xf --logtarget=sysout start From 2224b3db4a5e9bf045131f02bd92cec8945e1e7d Mon Sep 17 00:00:00 2001 From: Evan Linde Date: Thu, 7 Apr 2022 20:51:20 -0500 Subject: [PATCH 04/87] extend date regex to include xrdp's %Y%m%d-%H:%M:%S format --- fail2ban/server/datedetector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fail2ban/server/datedetector.py b/fail2ban/server/datedetector.py index b90e1b26..53d055bc 100644 --- a/fail2ban/server/datedetector.py +++ b/fail2ban/server/datedetector.py @@ -165,8 +165,8 @@ class DateDetectorCache(object): r"%b %d, %ExY %I:%M:%S %p", # ASSP: Apr-27-13 02:33:06 r"^%b-%d-%Exy %k:%M:%S", - # 20050123T215959, 20050123 215959, 20050123 85959 - r"%ExY%Exm%Exd(?:T| ?)%ExH%ExM%ExS(?:[.,]%f)?(?:\s*%z)?", + # 20050123T215959, 20050123 215959, 20050123 85959, 20050123-21:59:59 + r"%ExY%Exm%Exd(?:-|T| ?)%ExH:?%ExM:?%ExS(?:[.,]%f)?(?:\s*%z)?", # prefixed with optional named time zone (monit): # PDT Apr 16 21:05:29 r"(?:%Z )?(?:%a )?%b %d %k:%M:%S(?:\.%f)?(?: %ExY)?", From 11768a97e96d1f0c73e582c7f34e4b1c21a76387 Mon Sep 17 00:00:00 2001 From: Evan Linde Date: Thu, 7 Apr 2022 21:34:33 -0500 Subject: [PATCH 05/87] add filter for xrdp --- ChangeLog | 1 + config/filter.d/xrdp.conf | 34 +++++++++++++++++++++++++++++++ config/jail.conf | 6 ++++++ fail2ban/tests/files/logs/xrdp | 37 ++++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+) create mode 100644 config/filter.d/xrdp.conf create mode 100644 fail2ban/tests/files/logs/xrdp diff --git a/ChangeLog b/ChangeLog index d386ceef..cb2959a5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -45,6 +45,7 @@ ver. 1.0.1-dev-1 (20??/??/??) - development nightly edition - 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 +* `filter.d/xrdp.conf` - new filter for XRDP, an open source RDP server ver. 0.11.2 (2020/11/23) - heal-the-world-with-security-tools diff --git a/config/filter.d/xrdp.conf b/config/filter.d/xrdp.conf new file mode 100644 index 00000000..c625b496 --- /dev/null +++ b/config/filter.d/xrdp.conf @@ -0,0 +1,34 @@ +# +# Fail2Ban filter for XRDP +# +# Detects login attempts with invalid credentials +# +# Requirements: +# - xrdp >= 0.9.19 +# - The log level in sesman.ini should be set to `INFO` or higher +# to emit the log messages needed for this filter. +# +# Author: Evan Linde +# + +[INCLUDES] + +# Read common prefixes. If any customizations available -- read them from +# common.local +before = common.conf + + +[DEFAULT] + +_daemon = xrdp-sesman + + +[Definition] + +authfail_re = \[INFO \] AUTHFAIL: user=.+ ip= time=\d+ + +failregex = ^%(__prefix_line)s%(authfail_re)s$ + +ignoreregex = + + diff --git a/config/jail.conf b/config/jail.conf index fe8db527..f04e8be7 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -978,3 +978,9 @@ banaction = %(banaction_allports)s [monitorix] port = 8080 logpath = /var/log/monitorix-httpd + + +[xrdp] +port = 3389 +logpath = /var/log/xrdp-sesman.log + diff --git a/fail2ban/tests/files/logs/xrdp b/fail2ban/tests/files/logs/xrdp new file mode 100644 index 00000000..1a9e8ee2 --- /dev/null +++ b/fail2ban/tests/files/logs/xrdp @@ -0,0 +1,37 @@ +# +# /var/log/xrdp-sesman.log -- should be about the same on any linux distro +# + +# failJSON: { "time": "2022-04-07T12:11:06", "match": true, "host": "10.171.161.151"} +[20220407-12:11:06] [INFO ] AUTHFAIL: user=badtypist ip=::ffff:10.171.161.151 time=1649351466 + +# ip injection: 10.171.161.151 should be matched as the host; 192.168.0.1 is an innocent, injected address +# failJSON: { "time": "2022-04-07T12:11:24", "match": true, "host": "10.171.161.151", "desc": "specifying ip address as username"} +[20220407-12:11:24] [INFO ] AUTHFAIL: user=192.168.0.1 ip=::ffff:10.171.161.151 time=1649351484 + +# ip injection: 10.171.161.151 should be matched as the host; 192.168.0.4 is an innocent, injected address +# failJSON: { "time": "2022-04-07T12:22:02", "match": true, "host": "10.171.161.151", "desc": "more devious log injection"} +[20220407-12:22:02] [INFO ] AUTHFAIL: user=loginjector ip=192.168.0.4 time=123456789\n[20220407-12:16:59] [INFO ] AUTHFAIL: user=endinjection ip=::ffff:10.171.161.151 time=1649352122 + + +# +# /var/log/messages -- RHEL/Fedora family +# + +# failJSON: { "time": "2005-04-07T12:11:06", "match": true, "host": "10.171.161.151"} +Apr 7 12:11:06 servername xrdp-sesman[41441]: [INFO ] AUTHFAIL: user=badtypist ip=::ffff:10.171.161.151 time=1649351466 + +# ip injection: 10.171.161.151 should be matched as the host; 192.168.0.1 is an innocent, injected address +# failJSON: { "time": "2005-04-07T12:11:24", "match": true, "host": "10.171.161.151", "desc": "specifying ip address as username"} +Apr 7 12:11:24 servername xrdp-sesman[41441]: [INFO ] AUTHFAIL: user=192.168.0.1 ip=::ffff:10.171.161.151 time=1649351484 + +# ip injection: 10.171.161.151 should be matched as the host; 192.168.0.4 is an innocent, injected address +# failJSON: { "time": "2005-04-07T12:22:02", "match": true, "host": "10.171.161.151", "desc": "more devious log injection"} +Apr 7 12:22:02 servername xrdp-sesman[41441]: [INFO ] AUTHFAIL: user=loginjector ip=192.168.0.4 time=123456789\n[20220407-12:16:59] [INFO ] AUTHFAIL: user=endinjection ip=::ffff:10.171.161.151 time=1649352122 + +# ip injection: innocent, injected ip 192.168.0.4 in a line that shouldn't contain a host +# failJSON: { "match": false } +Apr 7 12:22:02 servername xrdp[52415]: [INFO ] xrdp_wm_log_msg: login failed for user loginjector ip=192.168.0.4 time=12345\n[20220407-12:16:59] [INFO ] AUTHFAIL: user=endinjection + +# failJSON: { "match": false } +Apr 7 12:22:02 servername xrdp[52415]: [INFO ] n[20220407-12:16:59] [INFO ] AUTHFAIL: user=endinjection From 6a2d2aa97a4675980a475fbdf9024b4bddf06a7f Mon Sep 17 00:00:00 2001 From: "Sergey G. Brester" Date: Fri, 8 Apr 2022 09:55:44 +0200 Subject: [PATCH 06/87] capture user, add datepattern (anchored exact main format and fail2ban defaults) --- config/filter.d/xrdp.conf | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/config/filter.d/xrdp.conf b/config/filter.d/xrdp.conf index c625b496..6937acb8 100644 --- a/config/filter.d/xrdp.conf +++ b/config/filter.d/xrdp.conf @@ -25,10 +25,11 @@ _daemon = xrdp-sesman [Definition] -authfail_re = \[INFO \] AUTHFAIL: user=.+ ip= time=\d+ +authfail_re = \[INFO \] AUTHFAIL: user=.+ ip= time=\d+ failregex = ^%(__prefix_line)s%(authfail_re)s$ ignoreregex = - +datepattern = ^\[?%%ExY%%Exm%%Exd[-|T]%%ExH:?%%ExM:?%%ExS(?:[.,]%%f)?(?:\s*%%z)?\]? + ^{DATE} From 64983ecc29239b015e324f23c1f4635c5d3164e9 Mon Sep 17 00:00:00 2001 From: evanlinde Date: Fri, 8 Apr 2022 09:48:35 -0500 Subject: [PATCH 07/87] Make added date pattern specific to xrdp's format --- config/filter.d/xrdp.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/filter.d/xrdp.conf b/config/filter.d/xrdp.conf index 6937acb8..803219f6 100644 --- a/config/filter.d/xrdp.conf +++ b/config/filter.d/xrdp.conf @@ -31,5 +31,5 @@ failregex = ^%(__prefix_line)s%(authfail_re)s$ ignoreregex = -datepattern = ^\[?%%ExY%%Exm%%Exd[-|T]%%ExH:?%%ExM:?%%ExS(?:[.,]%%f)?(?:\s*%%z)?\]? +datepattern = ^\[?%%ExY%%Exm%%Exd-%%ExH:%%ExM:%%ExS\]? ^{DATE} From 5a0224ff0e68c39239cc16f184b5dde15a2dc229 Mon Sep 17 00:00:00 2001 From: evanlinde Date: Fri, 8 Apr 2022 09:52:52 -0500 Subject: [PATCH 08/87] Use potentially faster regex for username match --- config/filter.d/xrdp.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/filter.d/xrdp.conf b/config/filter.d/xrdp.conf index 803219f6..253bcf8d 100644 --- a/config/filter.d/xrdp.conf +++ b/config/filter.d/xrdp.conf @@ -25,7 +25,7 @@ _daemon = xrdp-sesman [Definition] -authfail_re = \[INFO \] AUTHFAIL: user=.+ ip= time=\d+ +authfail_re = \[INFO \] AUTHFAIL: user=(?:\S+|.+) ip= time=\d+ failregex = ^%(__prefix_line)s%(authfail_re)s$ From 05575de1f1eb23f9f4c2c07c6b16ae383d12e3e1 Mon Sep 17 00:00:00 2001 From: Csillag Tamas Date: Mon, 30 May 2022 14:05:18 +0200 Subject: [PATCH 09/87] nftables.conf - add support for cidr notation Currently when trying to add an address like: 141.98.11.0/24 it fails with: fail2ban.utils [720]: ERROR 7fe8c36f6630 -- exec: nft add element inet f2b-table addr-set-custom \{ 141.98.11.0/24 \} fail2ban.utils [720]: ERROR 7fe8c36f6630 -- stderr: "Error: You must add 'flags interval' to your set declaration if you want to add prefix elements" After adding 'flags interval' one can ban ranges now as expected. --- config/action.d/nftables.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/action.d/nftables.conf b/config/action.d/nftables.conf index 77cf3661..b2bb9ec1 100644 --- a/config/action.d/nftables.conf +++ b/config/action.d/nftables.conf @@ -55,7 +55,7 @@ _nft_for_proto-multiport-done = done _nft_list = -a list chain _nft_get_handle_id = grep -oP '@\s+.*\s+\Khandle\s+(\d+)$' -_nft_add_set = add set
\{ type \; \} +_nft_add_set = add set
\{ type \; flags interval\; \} <_nft_for_proto--iter> add rule
%(rule_stat)s <_nft_for_proto--done> From 7d6b1a4c3b3a3f8e517ecf6c901d44fe9ab16c61 Mon Sep 17 00:00:00 2001 From: Ulrich Goettlich Date: Tue, 13 Jun 2023 09:00:38 +0200 Subject: [PATCH 10/87] Fixed startup on debian bookworm (using systemd as sshd_backend) --- config/paths-debian.conf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config/paths-debian.conf b/config/paths-debian.conf index 1f5ea37d..4e2847d5 100644 --- a/config/paths-debian.conf +++ b/config/paths-debian.conf @@ -28,3 +28,8 @@ exim_main_log = /var/log/exim4/mainlog proftpd_log = /var/log/proftpd/proftpd.log roundcube_errors_log = /var/log/roundcube/errors.log + +# These services will log to the journal via syslog, so use the journal by +# default. +sshd_backend = systemd +postfix_backend = systemd From dc899e438fc473366f02ddb6b9e84e1a24510c2b Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 7 Jul 2025 01:04:35 +0200 Subject: [PATCH 11/87] avoid error "Unable to get failures" by stop (if file gets removed from filter, but filter already entered getFailures for the file); closes gh-4032 --- fail2ban/server/filter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fail2ban/server/filter.py b/fail2ban/server/filter.py index 210ca084..942e0ba1 100644 --- a/fail2ban/server/filter.py +++ b/fail2ban/server/filter.py @@ -1111,8 +1111,8 @@ class FileFilter(Filter): 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) + if log is None and self.active: + logSys.log(logging.MSG, "Unable to get failures in %s", filename) return False # We should always close log (file), otherwise may be locked (log-rotate, etc.) try: From b710d5b6c7b0249a257246b15ce6408630a5fe9c Mon Sep 17 00:00:00 2001 From: sebres Date: Sun, 13 Jul 2025 01:03:53 +0200 Subject: [PATCH 12/87] `filter.d/sendmail-reject.conf` - also recognize "Domain of sender address ... does not resolve"; closes gh-4035 --- ChangeLog | 1 + config/filter.d/sendmail-reject.conf | 4 ++-- fail2ban/tests/files/logs/sendmail-reject | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5ccd2b7b..dc0bf8ec 100644 --- a/ChangeLog +++ b/ChangeLog @@ -84,6 +84,7 @@ ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition * `filter.d/dovecot.conf` - add support for latest Dovecot 2.4 release (gh-4016) * `filter.d/proxmox.conf` - add support to Proxmox Web GUI (gh-2966) * `filter.d/openvpn.conf` - new filter and jail for openvpn recognizing failed TLS handshakes (gh-2702) +* `filter.d/sendmail-reject.conf` - also recognize "Domain of sender address ... does not resolve" (gh-4035) * `filter.d/vaultwarden.conf` - new filter and jail for Vaultwarden (gh-3979) * `fail2ban-regex` extended with new option `-i` or `--invert` to output not-matched lines by `-o` or `--out` (gh-4001) diff --git a/config/filter.d/sendmail-reject.conf b/config/filter.d/sendmail-reject.conf index 41035e5f..f969a060 100644 --- a/config/filter.d/sendmail-reject.conf +++ b/config/filter.d/sendmail-reject.conf @@ -25,12 +25,12 @@ addr = (?:(?:IPv6:)?|) prefregex = ^%(__prefix_line)s.+$ -cmnfailre = ^ruleset=check_rcpt, arg1=(?P<\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))$ +cmnfailre = ^ruleset=check_rcpt, arg1=(?P<\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+)|[45]5[13] [45]\.1\.8(?: (?P=email)\.\.\.)? Domain of sender address \S+ does not (?:exist|resolve)|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$ - ^from=<[^@]+@[^>]+>, size=\d+, class=\d+, nrcpts=\d+, bodytype=\w+, proto=E?SMTP, daemon=MTA, relay=\S+ \[%(addr)s\]$ + ^from=<[^@]+@[^>]+>, size=\d+, class=\d+, nrcpts=\d+, bodytype=\w+, proto=E?SMTP, daemon=\S+, relay=\S+ \[%(addr)s\]$ mdre-normal = diff --git a/fail2ban/tests/files/logs/sendmail-reject b/fail2ban/tests/files/logs/sendmail-reject index 8debe7ca..c219578e 100644 --- a/fail2ban/tests/files/logs/sendmail-reject +++ b/fail2ban/tests/files/logs/sendmail-reject @@ -57,6 +57,9 @@ Feb 27 15:49:02 batman sm-mta[88377]: s1REn1un088377: ruleset=check_rcpt, arg1=< # failJSON: { "time": "2005-02-27T22:44:42", "match": true , "host": "123.69.106.50" } Feb 27 22:44:42 batman sm-mta[30972]: s1RLieRP030972: ruleset=check_rcpt, arg1=, relay=[123.69.106.50], reject=553 5.1.8 ... Domain of sender address lf@ibuv.net does not exist +# failJSON: { "time": "2005-02-27T22:44:43", "match": true , "host": "192.0.2.100" } +Feb 27 22:44:43 batman sm-mta[4012]: 56CF8Qni004012: ruleset=check_rcpt, arg1=, relay=[192.0.2.100] (may be forged), reject=451 4.1.8 Domain of sender address test.whatever@service.example.com does not resolve + # failJSON: { "time": "2005-02-23T21:18:47", "match": true , "host": "76.72.174.70" } Feb 23 21:18:47 batman sm-mta[93301]: s1NKIkZa093301: [76.72.174.70]: EXPN root [rejected] From 1c2ace295821f983e95146de59dd63a5dc484abd Mon Sep 17 00:00:00 2001 From: sebres Date: Sun, 13 Jul 2025 01:08:50 +0200 Subject: [PATCH 13/87] GHA: update python 3.14.0-beta.4 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2c0ae72f..4f593361 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.8, 3.9, '3.10', '3.11', '3.12', '3.13', '3.14.0-alpha.6', pypy3.11] + python-version: [3.8, 3.9, '3.10', '3.11', '3.12', '3.13', '3.14.0-beta.4', pypy3.11] fail-fast: false # Steps represent a sequence of tasks that will be executed as part of the job steps: From e97df4672a666803db865f3f1eb76be47e8f38cc Mon Sep 17 00:00:00 2001 From: 177ac Date: Sun, 20 Jul 2025 18:05:35 +0900 Subject: [PATCH 14/87] filter.d/asterisk: fix regex to match "No matching endpoint found" with retry info --- config/filter.d/asterisk.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/filter.d/asterisk.conf b/config/filter.d/asterisk.conf index 0f801e0b..4aae47d3 100644 --- a/config/filter.d/asterisk.conf +++ b/config/filter.d/asterisk.conf @@ -27,7 +27,8 @@ failregex = ^Registration from '[^']*' failed for '(:\d+)?' - (?:Wrong pas ^hacking attempt detected ''$ ^SecurityEvent="(?:FailedACL|InvalidAccountID|ChallengeResponseFailed|InvalidPassword)"(?:(?:,(?!RemoteAddress=)\w+="[^"]*")*|.*?),RemoteAddress="IPV[46]/[^/"]+//\d+"(?:,(?!RemoteAddress=)\w+="[^"]*")*$ ^"Rejecting unknown SIP connection from (?::\d+)?"$ - ^Request (?:'[^']*' )?from '(?:[^']*|.*?)' failed for '(?::\d+)?'\s\(callid: [^\)]*\) - (?:No matching endpoint found|Not match Endpoint(?: Contact)? ACL|(?:Failed|Error) to authenticate)\s*$ + ^Request (?:'[^']*' )?from '(?:[^']*|.*?)' failed for '(?::\d+)?'\s\(callid: [^\)]*\) - No matching endpoint found(?:\s+after\s+\d+\s+tries\s+in\s+[\d.]+\s+ms)?\s*$ + ^Request (?:'[^']*' )?from '(?:[^']*|.*?)' failed for '(?::\d+)?'\s\(callid: [^\)]*\) - (?:Not match Endpoint(?: Contact)? ACL|(?:Failed|Error) to authenticate)\s*$ # FreePBX (todo: make optional in v.0.10): # ^(%(__prefix_line)s|\[\]\s*WARNING%(__pid_re)s:?(?:\[C-[\da-f]*\])? )[^:]+: Friendly Scanner from $ From b309cf6b3c065387f593f32d520ddfe1243f31c5 Mon Sep 17 00:00:00 2001 From: 177ac Date: Sun, 20 Jul 2025 18:06:33 +0900 Subject: [PATCH 15/87] Add test line --- fail2ban/tests/files/logs/asterisk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fail2ban/tests/files/logs/asterisk b/fail2ban/tests/files/logs/asterisk index 7f2ec967..fc497852 100644 --- a/fail2ban/tests/files/logs/asterisk +++ b/fail2ban/tests/files/logs/asterisk @@ -108,6 +108,8 @@ Nov 4 18:30:40 localhost asterisk[32229]: NOTICE[32257]: chan_sip.c:23417 in han # PJSip Errors # failJSON: { "time": "2016-05-06T07:08:09", "match": true, "host": "192.0.2.6" } [2016-05-06 07:08:09] NOTICE[17103] res_pjsip/pjsip_distributor.c: Request from '"test1" ' failed for '192.0.2.6:5678' (callid: deadbeef) - No matching endpoint found +# failJSON: { "time": "2016-05-06T07:08:09", "match": true, "host": "192.0.2.7", "desc": "Test for No matching endpoint found with retry counts (pattern 1)" } +[2016-05-06 07:08:09] NOTICE[17103] res_pjsip/pjsip_distributor.c: Request 'INVITE' from '"test2" ' failed for '192.0.2.7:5679' (callid: cafebabe) - No matching endpoint found after 5 tries in 2.500 ms # # FreePBX Warnings # #_dis_failJSON: { "time": "2016-05-06T07:08:09", "match": true, "host": "192.0.2.4" } From 6d3bfa8781a79bd838a999b58ae587c4ed2aedbd Mon Sep 17 00:00:00 2001 From: "Sergey G. Brester" Date: Sun, 20 Jul 2025 15:04:15 +0200 Subject: [PATCH 16/87] revert RE back, but relive the end-anchor a bit (ignore any text without single quote, so also preventing false match by injection on foreign data) --- config/filter.d/asterisk.conf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/filter.d/asterisk.conf b/config/filter.d/asterisk.conf index 4aae47d3..631ccbbc 100644 --- a/config/filter.d/asterisk.conf +++ b/config/filter.d/asterisk.conf @@ -27,8 +27,7 @@ failregex = ^Registration from '[^']*' failed for '(:\d+)?' - (?:Wrong pas ^hacking attempt detected ''$ ^SecurityEvent="(?:FailedACL|InvalidAccountID|ChallengeResponseFailed|InvalidPassword)"(?:(?:,(?!RemoteAddress=)\w+="[^"]*")*|.*?),RemoteAddress="IPV[46]/[^/"]+//\d+"(?:,(?!RemoteAddress=)\w+="[^"]*")*$ ^"Rejecting unknown SIP connection from (?::\d+)?"$ - ^Request (?:'[^']*' )?from '(?:[^']*|.*?)' failed for '(?::\d+)?'\s\(callid: [^\)]*\) - No matching endpoint found(?:\s+after\s+\d+\s+tries\s+in\s+[\d.]+\s+ms)?\s*$ - ^Request (?:'[^']*' )?from '(?:[^']*|.*?)' failed for '(?::\d+)?'\s\(callid: [^\)]*\) - (?:Not match Endpoint(?: Contact)? ACL|(?:Failed|Error) to authenticate)\s*$ + ^Request (?:'[^']*' )?from '(?:[^']*|.*?)' failed for '(?::\d+)?'\s\(callid: [^\)]*\) - (?:No matching endpoint found|Not match Endpoint(?: Contact)? ACL|(?:Failed|Error) to authenticate)\b[^']*$ # FreePBX (todo: make optional in v.0.10): # ^(%(__prefix_line)s|\[\]\s*WARNING%(__pid_re)s:?(?:\[C-[\da-f]*\])? )[^:]+: Friendly Scanner from $ From 7bb86822d0b1970baee4f1abb2580db06e209d25 Mon Sep 17 00:00:00 2001 From: "Sergey G. Brester" Date: Sun, 20 Jul 2025 15:15:38 +0200 Subject: [PATCH 17/87] Update ChangeLog --- ChangeLog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ChangeLog b/ChangeLog index dc0bf8ec..7122879a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -37,6 +37,8 @@ ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition - rename `ipsettype` to `ipsetbackend` (gh-2620), parameter `ipsettype` will be used now to the real set type (gh-3760) * `filter.d/apache-noscript.conf` - consider new log-format with "AH02811: stderr from /..." (gh-3900) * `filter.d/apache-overflows.conf` - consider AH10244: invalid URI path (gh-3778, gh-3900) +* `filter.d/asterisk.conf` - fixed RE for "no matching endpoint" with retry info (like `after X tries in Y ms`) at end, + loosening of end anchor (ignore any simple text tokens at end if no single quote found), gh-4037 * `filter.d/exim.conf`: - several rules of mode `normal` moved to new mode `more`, because of too risky handling (gh-3940), thereby mode `aggressive` is not affected, because it fully includes mode `more` now; From d86a7aecca9a780b5e8fbebcb74a9f72d9273185 Mon Sep 17 00:00:00 2001 From: sebres Date: Thu, 31 Jul 2025 17:38:28 +0200 Subject: [PATCH 18/87] amend to #3979: removed mistaken double pipes in group matches --- config/filter.d/vaultwarden.conf | 2 +- fail2ban/tests/files/logs/vaultwarden | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/config/filter.d/vaultwarden.conf b/config/filter.d/vaultwarden.conf index 63d78937..38bac51f 100644 --- a/config/filter.d/vaultwarden.conf +++ b/config/filter.d/vaultwarden.conf @@ -4,5 +4,5 @@ [Definition] -failregex = ^\s*(?:\[\]\s*)?\[vaultwarden::api::(identity||admin||core::two_factor::authenticator)\]\[ERROR\] (Invalid admin token||Invalid TOTP code||Username or password is incorrect)[\.!](?:\s+(?!IP:)\S+)* IP: (?:\. Username: \S+)? +failregex = ^\s*(?:\[\]\s*)?\[vaultwarden::api::(?:identity|admin|core::two_factor::authenticator)?\]\[ERROR\] (?:Invalid admin token|Invalid TOTP code|Username or password is incorrect)[\.!](?:\s+(?!IP:)\S+)* IP: (?:\. Username: \S+)? ignoreregex = diff --git a/fail2ban/tests/files/logs/vaultwarden b/fail2ban/tests/files/logs/vaultwarden index f797eeaf..ededb820 100644 --- a/fail2ban/tests/files/logs/vaultwarden +++ b/fail2ban/tests/files/logs/vaultwarden @@ -21,3 +21,6 @@ # failJSON: { "time": "2024-08-30T20:11:28", "match": true , "host": "2001:db8::b6d3:95d7:1425:766d" } [2024-08-31 02:11:28.892+0800][vaultwarden::api::core::two_factor::authenticator][ERROR] Invalid TOTP code! Server time: 2024-08-30 18:11:28 UTC IP: 2001:db8::b6d3:95d7:1425:766d + +# failJSON: { "time": "2024-08-30T20:11:30", "match": true , "host": "192.0.2.7" } +[2024-08-31 02:11:30.123+0800][vaultwarden::api::admin][ERROR] Invalid admin token! IP: 192.0.2.7. Username: alice From 0a91bf69a55c028ce26b2aaf8427de028678bcae Mon Sep 17 00:00:00 2001 From: bill Date: Mon, 4 Aug 2025 00:27:45 -0400 Subject: [PATCH 19/87] add filter for delayed requests and connection limiting --- config/filter.d/nginx-limit-req.conf | 4 +++- fail2ban/tests/files/logs/nginx-limit-req | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/config/filter.d/nginx-limit-req.conf b/config/filter.d/nginx-limit-req.conf index 29d37d09..b6fbe49d 100644 --- a/config/filter.d/nginx-limit-req.conf +++ b/config/filter.d/nginx-limit-req.conf @@ -36,6 +36,7 @@ before = nginx-error-common.conf # ngx_limit_req_zones = lr_zone|lr_zone2 # ngx_limit_req_zones = [^"]+ +ngx_limit_con_zones = [^"]+ # Depending on limit_req_log_level directive (may be: info | notice | warn | error): __err_type = [a-z]+ @@ -46,7 +47,8 @@ __err_type = [a-z]+ # failregex = ^%(__prefix_line)slimiting requests, excess: [\d\.]+ by zone "(?:%(ngx_limit_req_zones)s)", client: , server: \S*, request: "\S+ \S+ HTTP/\d+\.\d+", host: "\S+"(, referrer: "\S+")?\s*$ # Shortly, much faster and stable version of regexp: -failregex = ^%(__prefix_line)slimiting requests, excess: [\d\.]+ by zone "(?:%(ngx_limit_req_zones)s)", client: , +failregex = ^%(__prefix_line)s(?:limiting|delaying) requests?, excess: [\d\.]+,? by zone "(?:%(ngx_limit_req_zones)s)", client: , + ^%(__prefix_line)s(?:limiting|delaying) connections by zone "(?:%(ngx_limit_con_zones)s)", client: , ignoreregex = diff --git a/fail2ban/tests/files/logs/nginx-limit-req b/fail2ban/tests/files/logs/nginx-limit-req index 78367553..0a4ccfbc 100644 --- a/fail2ban/tests/files/logs/nginx-limit-req +++ b/fail2ban/tests/files/logs/nginx-limit-req @@ -11,6 +11,18 @@ # failJSON: { "time": "2016-09-30T08:36:06", "match": true, "host": "2001:db8::80da:af6b:8b2c" } 2016/09/30 08:36:06 [error] 22923#0: *4758725916 limiting requests, excess: 15.243 by zone "one", client: 2001:db8::80da:af6b:8b2c, server: example.com, request: "GET / HTTP/1.1", host: "example.com" +# failJSON: { "time": "2025-08-01T04:24:17", "match": true , "host": "206.189.215.97" } +2025/08/01 04:24:17 [warn] 4772#4772: *68 delaying request, excess: 0.841, by zone "req_limit", client: 206.189.215.97, server: myserver.net, request: "GET /ab2h HTTP/1.1", host: "22.18.134.49" + +# failJSON: { "time": "2025-08-01T14:32:27", "match": true , "host": "104.248.81.143" } +2025/08/01 14:32:27 [warn] 31733#31733: *21 delaying request, excess: 0.850, by zone "req_limit", client: 104.248.81.143, server: myserver.net, request: "GET /favicon.ico HTTP/1.1", host: "myserver.net", referrer: "https://myserver.net/" + +# failJSON: { "time": "2025-08-03T03:17:28", "match": true , "host": "128.199.22.141" } +2025/08/03 03:17:28 [error] 25808#25808: *598 limiting connections by zone "conn_limit", client: 128.199.22.141, server: myserver.net, request: "GET /favicon.ico HTTP/1.1", host: "84.108.142.49", referrer: "https://xxx.com/" + +# failJSON: { "time": "2025-08-03T13:56:22", "match": true , "host": "162.240.161.123" } +2025/08/03 13:56:22 [error] 943#943: *60 limiting connections by zone "conn_limit", client: 162.240.161.123, server: myserver.net, request: "GET /.env HTTP/1.1", host: "app.myserver.net" + # filterOptions: [{"logtype": "journal"}] # failJSON: { "match": true , "host": "192.0.2.2" } From e6516fd2b34f1a79ae50ea0c625e18fc9ce57709 Mon Sep 17 00:00:00 2001 From: "Sergey G. Brester" Date: Mon, 4 Aug 2025 11:24:51 +0200 Subject: [PATCH 20/87] combine 2 REs to single regex closes gh-3674 --- config/filter.d/nginx-limit-req.conf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/filter.d/nginx-limit-req.conf b/config/filter.d/nginx-limit-req.conf index b6fbe49d..51f4e08f 100644 --- a/config/filter.d/nginx-limit-req.conf +++ b/config/filter.d/nginx-limit-req.conf @@ -47,8 +47,7 @@ __err_type = [a-z]+ # failregex = ^%(__prefix_line)slimiting requests, excess: [\d\.]+ by zone "(?:%(ngx_limit_req_zones)s)", client: , server: \S*, request: "\S+ \S+ HTTP/\d+\.\d+", host: "\S+"(, referrer: "\S+")?\s*$ # Shortly, much faster and stable version of regexp: -failregex = ^%(__prefix_line)s(?:limiting|delaying) requests?, excess: [\d\.]+,? by zone "(?:%(ngx_limit_req_zones)s)", client: , - ^%(__prefix_line)s(?:limiting|delaying) connections by zone "(?:%(ngx_limit_con_zones)s)", client: , +failregex = ^%(__prefix_line)s(?:limiting|delaying) (?:request|connection)s?(?:, excess: [\d\.]+,?)? by zone "(?:%(ngx_limit_req_zones)s)", client: , ignoreregex = From dd58d440bc71f2705ca468bac3965b9f0a36ce24 Mon Sep 17 00:00:00 2001 From: "Sergey G. Brester" Date: Mon, 4 Aug 2025 11:32:10 +0200 Subject: [PATCH 21/87] Update ChangeLog --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index 5ccd2b7b..74141b09 100644 --- a/ChangeLog +++ b/ChangeLog @@ -82,6 +82,7 @@ ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition * `action.d/firewallcmd-rich-*.conf` - fixed incorrect quoting, disabling port variable expansion by substitution of rich rule (gh-3815) * `filter.d/dovecot.conf` - add support for latest Dovecot 2.4 release (gh-4016) +* `filter.d/nginx-limit-req.conf` - extended to ban hosts failed by limit connection in ngx_http_limit_conn_module (gh-3674, gh-4047) * `filter.d/proxmox.conf` - add support to Proxmox Web GUI (gh-2966) * `filter.d/openvpn.conf` - new filter and jail for openvpn recognizing failed TLS handshakes (gh-2702) * `filter.d/vaultwarden.conf` - new filter and jail for Vaultwarden (gh-3979) From 6120a731d97b170e7902bf1c8c1da98b2ada4686 Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 4 Aug 2025 15:16:26 -0400 Subject: [PATCH 22/87] update nginx limit-req filter again (#4048) amend to #4047 - removes unused ngx_limit_con_zones parameter. --- config/filter.d/nginx-limit-req.conf | 1 - 1 file changed, 1 deletion(-) diff --git a/config/filter.d/nginx-limit-req.conf b/config/filter.d/nginx-limit-req.conf index 51f4e08f..98235bb2 100644 --- a/config/filter.d/nginx-limit-req.conf +++ b/config/filter.d/nginx-limit-req.conf @@ -36,7 +36,6 @@ before = nginx-error-common.conf # ngx_limit_req_zones = lr_zone|lr_zone2 # ngx_limit_req_zones = [^"]+ -ngx_limit_con_zones = [^"]+ # Depending on limit_req_log_level directive (may be: info | notice | warn | error): __err_type = [a-z]+ From eb80b895d1e17baf3eaf2f4440ee61d1bbdf0870 Mon Sep 17 00:00:00 2001 From: "Sergey G. Brester" Date: Fri, 8 Aug 2025 10:10:40 +0200 Subject: [PATCH 23/87] provides flags interval as `addr_options` now --- config/action.d/nftables.conf | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/config/action.d/nftables.conf b/config/action.d/nftables.conf index b2bb9ec1..eeb98a5d 100644 --- a/config/action.d/nftables.conf +++ b/config/action.d/nftables.conf @@ -55,7 +55,7 @@ _nft_for_proto-multiport-done = done _nft_list = -a list chain
_nft_get_handle_id = grep -oP '@\s+.*\s+\Khandle\s+(\d+)$' -_nft_add_set = add set
\{ type \; flags interval\; \} +_nft_add_set = add set
\{ type \; \} <_nft_for_proto--iter> add rule
%(rule_stat)s <_nft_for_proto--done> @@ -197,6 +197,11 @@ addr_set = addr-set- # Values: [ ip | ip6 ] addr_family = ip +# Option: addr_options +# Notes: Additional options for the addr-set, by default allows to store CIDR or address ranges. +# Can be set to empty value to create simple addresses set. +addr_options = flags interval\; + [Init?family=inet6] addr_family = ip6 addr_type = ipv6_addr From dc3268ce5dd41e3dd896ce376cf9f21df01d4bcb Mon Sep 17 00:00:00 2001 From: "Sergey G. Brester" Date: Fri, 8 Aug 2025 10:16:01 +0200 Subject: [PATCH 24/87] servertestcase.py: adjust test coverage --- fail2ban/tests/servertestcase.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fail2ban/tests/servertestcase.py b/fail2ban/tests/servertestcase.py index 62ae81fd..fca0818a 100644 --- a/fail2ban/tests/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -1360,11 +1360,11 @@ class ServerConfigReaderTests(LogCaptureTestCase): r"`done`", ), 'ip4-start': ( - r"`nft add set inet f2b-table addr-set-j-w-nft-mp \{ type ipv4_addr\; \}`", + r"`nft add set inet f2b-table addr-set-j-w-nft-mp \{ type ipv4_addr\; flags interval\; \}`", r"`nft add rule inet f2b-table f2b-chain $proto dport \{ $(echo 'http,https' | sed s/:/-/g) \} ip saddr @addr-set-j-w-nft-mp reject`", ), 'ip6-start': ( - r"`nft add set inet f2b-table addr6-set-j-w-nft-mp \{ type ipv6_addr\; \}`", + r"`nft add set inet f2b-table addr6-set-j-w-nft-mp \{ type ipv6_addr\; flags interval\; \}`", r"`nft add rule inet f2b-table f2b-chain $proto dport \{ $(echo 'http,https' | sed s/:/-/g) \} ip6 saddr @addr6-set-j-w-nft-mp reject`", ), 'flush': ( @@ -1406,11 +1406,11 @@ class ServerConfigReaderTests(LogCaptureTestCase): r"`nft -- add chain inet f2b-table f2b-chain \{ type filter hook input priority -1 \; \}`", ), 'ip4-start': ( - r"`nft add set inet f2b-table addr-set-j-w-nft-ap \{ type ipv4_addr\; \}`", + r"`nft add set inet f2b-table addr-set-j-w-nft-ap \{ type ipv4_addr\; flags interval\; \}`", r"`nft add rule inet f2b-table f2b-chain meta l4proto \{ tcp,udp \} ip saddr @addr-set-j-w-nft-ap reject`", ), 'ip6-start': ( - r"`nft add set inet f2b-table addr6-set-j-w-nft-ap \{ type ipv6_addr\; \}`", + r"`nft add set inet f2b-table addr6-set-j-w-nft-ap \{ type ipv6_addr\; flags interval\; \}`", r"`nft add rule inet f2b-table f2b-chain meta l4proto \{ tcp,udp \} ip6 saddr @addr6-set-j-w-nft-ap reject`", ), 'flush': ( From bf4903538d619bfde2264b2c10dcf57a4e4c8df1 Mon Sep 17 00:00:00 2001 From: "Sergey G. Brester" Date: Fri, 8 Aug 2025 10:29:02 +0200 Subject: [PATCH 25/87] update ChangeLog (enhancement from #3291) --- ChangeLog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ChangeLog b/ChangeLog index 7a20de42..71546408 100644 --- a/ChangeLog +++ b/ChangeLog @@ -82,6 +82,9 @@ ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition - parameter `ipsettype` to set type of ipset, e. g. hash:ip, hash:net, etc (gh-3760) * `action.d/iptables.conf` - action and few derivatives of it extended to handle multiple chains, e. g. would also accept `chain = INPUT,FORWARD` (gh-3909) +* `action.d/nftables.conf` (gh-3291): + - new parameter `addr_options` for addr-set (default `flags interval\;`, allows to store CIDR or address ranges); + can be set to empty value to create simple addresses set (restore previous behavior). * `action.d/firewallcmd-rich-*.conf` - fixed incorrect quoting, disabling port variable expansion by substitution of rich rule (gh-3815) * `filter.d/dovecot.conf` - add support for latest Dovecot 2.4 release (gh-4016) From 3ce6f344e3c988306d92982ba6699fa31304a956 Mon Sep 17 00:00:00 2001 From: sebres Date: Tue, 12 Aug 2025 23:26:42 +0200 Subject: [PATCH 26/87] fixes beautifier `get` `ignoreip` (explicit convert to string) --- fail2ban/client/beautifier.py | 4 ++-- fail2ban/tests/clientbeautifiertestcase.py | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/fail2ban/client/beautifier.py b/fail2ban/client/beautifier.py index 21c49b94..4b61baa3 100644 --- a/fail2ban/client/beautifier.py +++ b/fail2ban/client/beautifier.py @@ -212,8 +212,8 @@ class Beautifier: else: msg = "These IP addresses/networks are ignored:\n" for ip in response[:-1]: - msg += "|- " + ip + "\n" - msg += "`- " + response[-1] + msg += "|- " + str(ip) + "\n" + msg += "`- " + str(response[-1]) elif inC[2] in ("failregex", "addfailregex", "delfailregex", "ignoreregex", "addignoreregex", "delignoreregex"): if len(response) == 0: diff --git a/fail2ban/tests/clientbeautifiertestcase.py b/fail2ban/tests/clientbeautifiertestcase.py index 5fcb2404..b7fff2bd 100644 --- a/fail2ban/tests/clientbeautifiertestcase.py +++ b/fail2ban/tests/clientbeautifiertestcase.py @@ -25,7 +25,7 @@ import unittest from ..client.beautifier import Beautifier from ..version import version -from ..server.ipdns import IPAddr +from ..server.ipdns import IPAddr, FileIPAddrSet from ..exceptions import UnknownJailException, DuplicateJailException class BeautifierTest(unittest.TestCase): @@ -297,6 +297,13 @@ class BeautifierTest(unittest.TestCase): output += "`- 10.0.2.1" self.assertEqual(self.b.beautify(response), output) + def testIgnoreIPFile(self): + self.b.setInputCmd(["set", "sshd", "addignoreip"]) + response = [FileIPAddrSet("/test/file-ipaddr-set")] + output = ("These IP addresses/networks are ignored:\n" + "`- file://test/file-ipaddr-set") + self.assertEqual(self.b.beautify(response), output) + def testFailRegex(self): self.b.setInputCmd(["get", "sshd", "failregex"]) output = "No regular expression is defined" From 4e22c20559ea6488bf9a8e84c5f7d8bff5a52c34 Mon Sep 17 00:00:00 2001 From: sebres Date: Tue, 12 Aug 2025 23:46:10 +0200 Subject: [PATCH 27/87] fixes `ignoreip` prefix `file://` - it shall resolve absolute file name (starting with `/`) unless it starts with `./`; relative paths are based relative the working dir; to use it relative current config root (normally `/etc/fail2ban`), one can use interpolation `%(fail2ban_confpath)s`, e.g.: file://%(fail2ban_confpath)s/ignore-ipaddr-file --- fail2ban/server/ipdns.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/fail2ban/server/ipdns.py b/fail2ban/server/ipdns.py index d188d5c8..02e65194 100644 --- a/fail2ban/server/ipdns.py +++ b/fail2ban/server/ipdns.py @@ -739,11 +739,11 @@ class IPAddrSet(set): class FileIPAddrSet(IPAddrSet): - # RE matching file://... - RE_FILE_IGN_IP = re.compile(r'^file:/{0,2}(.*)$') + # RE matching file://... (absolute as well as relative file name) + RE_FILE_IGN_IP = re.compile(r'^file:(?:/{0,2}(?=/(?!/|.{1,2}/))|/{0,2})(.*)$') fileName = '' - _shortRepr = None + _reprName = None maxUpdateLatency = 1 # latency in seconds to update by changes _nextCheck = 0 _fileStats = () @@ -798,12 +798,9 @@ class FileIPAddrSet(IPAddrSet): logSys.warning("Retrieving IPs set from %r failed: %s", self.fileName, e) def __repr__(self): - if not self._shortRepr: - shortfn = os.path.basename(self.fileName) - if shortfn != self.fileName: - shortfn = '.../' + shortfn - self._shortRepr = 'file:' + shortfn + ')' - return self._shortRepr + if self._reprName is None: + self._reprName = 'file:' + ('/' if self.fileName.startswith('/') else '') + self.fileName + return self._reprName def __contains__(self, ip): # load if needed: From bdb5d999068c8ef9a33bf3f8fbae0da512d056c9 Mon Sep 17 00:00:00 2001 From: sebres Date: Tue, 19 Aug 2025 11:37:01 +0200 Subject: [PATCH 28/87] Log `Repeal Ban` instead of `Unban` on stop action, jail or fail2ban, because the tickets are "unbanned" temporary (till restart); closes gh-4057 --- fail2ban/server/actions.py | 8 ++++---- fail2ban/tests/fail2banclienttestcase.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/fail2ban/server/actions.py b/fail2ban/server/actions.py index 26e80107..581c5724 100644 --- a/fail2ban/server/actions.py +++ b/fail2ban/server/actions.py @@ -640,12 +640,12 @@ class Actions(JailThread, Mapping): If actions specified, don't flush list - just execute unban for given actions (reload, obsolete resp. removed actions). """ - log = True + log = "Unban" if not stop else "Repeal Ban" if actions is None: logSys.debug(" Flush ban list") lst = self.banManager.flushBanList() else: - log = False # don't log "[jail] Unban ..." if removing actions only. + log = None # don't log "[jail] Unban ..." if removing actions only. lst = iter(self.banManager) cnt = 0 # first we'll execute flush for actions supporting this operation: @@ -686,7 +686,7 @@ class Actions(JailThread, Mapping): cnt, self.banManager.size(), self._jail.name) return cnt - def __unBan(self, ticket, actions=None, log=True): + def __unBan(self, ticket, actions=None, log="Unban"): """Unbans host corresponding to the ticket. Executes the actions in order to unban the host given in the @@ -704,7 +704,7 @@ class Actions(JailThread, Mapping): ip = ticket.getID() aInfo = self._getActionInfo(ticket) if log: - logSys.notice("[%s] Unban %s", self._jail.name, ip) + logSys.notice("[%s] %s %s", self._jail.name, log, ip) for name, action in unbactions.items(): try: logSys.debug("[%s] action %r: unban %s", self._jail.name, name, ip) diff --git a/fail2ban/tests/fail2banclienttestcase.py b/fail2ban/tests/fail2banclienttestcase.py index b3509739..c15e4978 100644 --- a/fail2ban/tests/fail2banclienttestcase.py +++ b/fail2ban/tests/fail2banclienttestcase.py @@ -1125,9 +1125,9 @@ class Fail2banServerTest(Fail2banClientServerBase): "3 ticket(s) in 'test-jail2", all=True, wait=MID_WAITTIME) # stop/start and unban/restore ban: self.assertLogged( - "[test-jail2] Unban 192.0.2.4", - "[test-jail2] Unban 192.0.2.8", - "[test-jail2] Unban 192.0.2.9", + "[test-jail2] Repeal Ban 192.0.2.4", + "[test-jail2] Repeal Ban 192.0.2.8", + "[test-jail2] Repeal Ban 192.0.2.9", "Jail 'test-jail2' stopped", "Jail 'test-jail2' started", "[test-jail2] Restore Ban 192.0.2.4", From c26fda9dbba3d3b6066ed57b62283c7a22302b97 Mon Sep 17 00:00:00 2001 From: sebres Date: Sat, 23 Aug 2025 20:16:07 +0200 Subject: [PATCH 29/87] `filter.d/dovecot.conf`: new matches in `aggressive` mode: - new variant for `no auth attempts in X secs` with `Login aborted` and `(no_auth_attempts)`; - covered `disconnected during TLS handshake` with `no application protocol` and `no shared cipher`. --- config/filter.d/dovecot.conf | 4 ++-- fail2ban/tests/files/logs/dovecot | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/config/filter.d/dovecot.conf b/config/filter.d/dovecot.conf index 66917fc8..8c3ba359 100644 --- a/config/filter.d/dovecot.conf +++ b/config/filter.d/dovecot.conf @@ -16,12 +16,12 @@ _bypass_reject_reason = (?:: (?:\w+\([^\):]*\) \w+|[^\(]+))* prefregex = ^%(__prefix_line)s(?:%(_auth_worker)s(?:\([^\)]+\))?: )?(?:%(__pam_auth)s(?:\(dovecot:auth\))?: |(?:pop3|imap|managesieve|submission)-login: )?(?:Info: )?%(_auth_worker_info)s.+$ failregex = ^authentication failure; logname=\S* uid=\S* euid=\S* tty=dovecot ruser=\S* rhost=(?:\s+user=\S*)?\s*$ - ^(?:Login aborted|Aborted login|Disconnected|Remote closed connection|Client has quit the connection)%(_bypass_reject_reason)s \((?:auth failed, \d+ attempts(?: in \d+ secs)?|tried to use (?:disabled|disallowed) \S+ auth|proxy dest auth failed)\)(?: \(auth_failed\))?:(?: user=<[^>]*>,)?(?: method=\S+,)? rip=(?:[^>]*(?:, session=<\S+>)?)\s*$ + ^(?:Login aborted|Aborted login|Disconnected|Remote closed connection|Client has quit the connection)%(_bypass_reject_reason)s \((?:auth failed, \d+ attempts(?: in \d+ secs)?|tried to use (?:disabled|disallowed) \S+ auth|proxy dest auth failed)\)[^:]*:(?: user=<[^>]*>,)?(?: method=\S+,)? rip=(?:[^>]*(?:, session=<\S+>)?)\s*$ ^pam\(\S+,(?:,\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*,(?:,\S*)?\): (?:[Uu]nknown user|[Ii]nvalid credentials|[Pp]assword mismatch) > -mdre-aggressive = ^(?:Aborted login|Disconnected|Remote closed connection|Client has quit the connection)%(_bypass_reject_reason)s \((?:no auth attempts|disconnected before auth was ready,|client didn't finish \S+ auth,)(?: (?:in|waited) \d+ secs)?\):(?: user=<[^>]*>,)?(?: method=\S+,)? rip=(?:[^>]*(?:, session=<\S+>)?)\s*$ +mdre-aggressive = ^(?:Login aborted|Aborted login|Disconnected|Remote closed connection|Client has quit the connection)%(_bypass_reject_reason)s \((?:no auth attempts|disconnected before auth was ready,|client didn't finish \S+ auth,|disconnected during TLS handshake)(?: (?:in|waited) \d+ secs)?\)[^:]*:(?: user=<[^>]*>,)?(?: method=\S+,)? rip=(?:[^>]*(?:, session=<\S+>)?)\s*$ mdre-normal = diff --git a/fail2ban/tests/files/logs/dovecot b/fail2ban/tests/files/logs/dovecot index 4f5a0b78..25a1f878 100644 --- a/fail2ban/tests/files/logs/dovecot +++ b/fail2ban/tests/files/logs/dovecot @@ -147,6 +147,9 @@ Aug 29 15:33:53 server dovecot: managesieve-login: Disconnected: Too many invali # failJSON: { "time": "2004-08-29T01:49:33", "match": true , "host": "192.0.2.5", "desc": "matches in aggressive mode, avoid slow RE, gh-3370" } Aug 29 01:49:33 server dovecot[459]: imap-login: Disconnected: Connection closed: read(size=1026) failed: Connection reset by peer (no auth attempts in 0 secs): user=<>, rip=192.0.2.5, lip=127.0.0.1, TLS handshaking: read(size=1026) failed: Connection reset by peer +# failJSON: { "time": "2004-08-22T12:09:41", "match": true , "host": "192.0.2.6", "desc": "matches in aggressive mode, new format with `Login aborted` and `(no_auth_attempts)`" } +Aug 22 12:09:41 server dovecot: imap-login: Login aborted: Connection closed: read(size=653) failed: Connection reset by peer (no auth attempts in 1 secs) (no_auth_attempts): user=<>, rip=192.0.2.6, lip=192.0.2.1, TLS: read(size=653) failed: Connection reset by peer, session= + # failJSON: { "time": "2004-08-29T01:49:33", "match": true , "host": "192.0.2.5", "desc": "matches in aggressive mode, avoid slow RE, gh-3370" } Aug 29 01:49:33 server dovecot[459]: imap-login: Disconnected: Connection closed: SSL_accept() failed: error:1408F10B:SSL routines:ssl3_get_record:wrong version number (no auth attempts in 0 secs): user=<>, rip=192.0.2.5, lip=127.0.0.1, TLS handshaking: SSL_accept() failed: error:1408F10B:SSL routines:ssl3_get_record:wrong version number # failJSON: { "time": "2004-08-29T01:49:33", "match": true , "host": "192.0.2.5", "desc": "matches in aggressive mode, avoid slow RE, gh-3370" } @@ -162,3 +165,8 @@ Aug 29 16:06:58 s166-62-100-187 dovecot: imap-login: Disconnected (disconnected Aug 31 16:15:10 s166-62-100-187 dovecot: imap-login: Disconnected (client didn't finish SASL auth, waited 2 secs): user=<>, method=PLAIN, rip=192.0.2.6, lip=192.168.1.2, TLS: SSL_read() syscall failed: Connection reset by peer, TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits) # failJSON: { "time": "2004-08-31T16:21:53", "match": true , "host": "192.0.2.7" } Aug 31 16:21:53 s166-62-100-187 dovecot: imap-login: Disconnected (no auth attempts in 4 secs): user=<>, rip=192.0.2.7, lip=192.168.1.2, TLS handshaking: SSL_accept() syscall failed: Connection reset by peer + +# failJSON: { "time": "2004-08-22T12:09:41", "match": true , "host": "192.0.2.7", "desc": "disconnected during TLS handshake (no application protocol)" } +Aug 22 12:09:41 server dovecot: imap-login: Login aborted: Connection closed: SSL_accept() failed: error:0A0000EB:SSL routines::no application protocol (disconnected during TLS handshake) (tls_handshake_not_finished): user=<>, rip=192.0.2.7, lip=192.0.2.1, TLS handshaking: SSL_accept() failed: error:0A0000EB:SSL routines::no application protocol, session= +# failJSON: { "time": "2004-08-22T12:09:41", "match": true , "host": "192.0.2.7", "desc": "disconnected during TLS handshake (no shared cipher)" } +Aug 22 12:09:41 server dovecot: imap-login: Login aborted: Connection closed: SSL_accept() failed: error:0A0000C1:SSL routines::no shared cipher (disconnected during TLS handshake) (tls_handshake_not_finished): user=<>, rip=192.0.2.7, lip=192.0.2.1, TLS handshaking: SSL_accept() failed: error:0A0000C1:SSL routines::no shared cipher, session= From 002719dca4f28e174c122a2c7d90ae1d8f53826a Mon Sep 17 00:00:00 2001 From: sebres Date: Sat, 23 Aug 2025 20:18:59 +0200 Subject: [PATCH 30/87] ChangeLog update --- ChangeLog | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 71546408..62b74fba 100644 --- a/ChangeLog +++ b/ChangeLog @@ -87,7 +87,10 @@ ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition can be set to empty value to create simple addresses set (restore previous behavior). * `action.d/firewallcmd-rich-*.conf` - fixed incorrect quoting, disabling port variable expansion by substitution of rich rule (gh-3815) -* `filter.d/dovecot.conf` - add support for latest Dovecot 2.4 release (gh-4016) +* `filter.d/dovecot.conf`: + - add support for latest Dovecot 2.4 release (gh-4016) + - mode `aggressive` covered new variant for `no auth attempts in X secs` with `Login aborted` and `(no_auth_attempts)` + - mode `aggressive` extended to match `disconnected during TLS handshake` with `no application protocol` and `no shared cipher` * `filter.d/nginx-limit-req.conf` - extended to ban hosts failed by limit connection in ngx_http_limit_conn_module (gh-3674, gh-4047) * `filter.d/proxmox.conf` - add support to Proxmox Web GUI (gh-2966) * `filter.d/openvpn.conf` - new filter and jail for openvpn recognizing failed TLS handshakes (gh-2702) From 0265df854e0db07d79da735e2a9bf4aee86f7975 Mon Sep 17 00:00:00 2001 From: sebres Date: Sat, 23 Aug 2025 22:00:03 +0200 Subject: [PATCH 31/87] silence skipping tests output for python versions that basically can not have the modules --- fail2ban/tests/action_d/test_smtp.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fail2ban/tests/action_d/test_smtp.py b/fail2ban/tests/action_d/test_smtp.py index 6ad99978..a6904255 100644 --- a/fail2ban/tests/action_d/test_smtp.py +++ b/fail2ban/tests/action_d/test_smtp.py @@ -175,7 +175,8 @@ try: super(SMTPActionTest, self).tearDown() except ImportError as e: - print("I: Skipping smtp tests: %s" % e) + if tuple(sys.version_info) <= (3, 11): + print("I: Skipping smtp tests: %s" % e) try: @@ -309,4 +310,5 @@ try: super(AIOSMTPActionTest, self).tearDown() except ImportError as e: - print("I: Skipping SSL smtp tests: %s" % e) + if tuple(sys.version_info) >= (3, 10): + print("I: Skipping SSL smtp tests: %s" % e) From a0555685004b5782edf939a016934a04ba990db5 Mon Sep 17 00:00:00 2001 From: sebres Date: Sat, 23 Aug 2025 22:10:55 +0200 Subject: [PATCH 32/87] GHA: update python 3.14.0-rc.2 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4f593361..cde56d34 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.8, 3.9, '3.10', '3.11', '3.12', '3.13', '3.14.0-beta.4', pypy3.11] + python-version: [3.8, 3.9, '3.10', '3.11', '3.12', '3.13', '3.14.0-rc.2', pypy3.11] fail-fast: false # Steps represent a sequence of tasks that will be executed as part of the job steps: From c9e1a1b087098770bc6e46c0dbe655afa421ae3f Mon Sep 17 00:00:00 2001 From: sebres Date: Sat, 23 Aug 2025 22:18:56 +0200 Subject: [PATCH 33/87] silence warning "Unknown distribution option: 'test_suite'", seems not work anymore (2.x only?) - test suite shall be invoked using `bin/fail2ban-testcases` --- setup.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/setup.py b/setup.py index ee9ea4df..b2c0e5c9 100755 --- a/setup.py +++ b/setup.py @@ -157,13 +157,6 @@ too many password failures. It updates firewall rules to reject the IP address or executes user defined commands.''' -if setuptools: - setup_extra = { - 'test_suite': "fail2ban.tests.utils.gatherTests", - } -else: - setup_extra = {} - data_files_extra = [] if os.path.exists('/var/run'): # if we are on the system with /var/run -- we are to use it for having fail2ban/ @@ -249,8 +242,7 @@ setup( ('/var/lib/fail2ban', '' ), - ] + data_files_extra, - **setup_extra + ] + data_files_extra ) # Do some checks after installation From 52399e6ef10f062ee7261fee21149907ed120336 Mon Sep 17 00:00:00 2001 From: sebres Date: Tue, 26 Aug 2025 18:03:46 +0200 Subject: [PATCH 34/87] amend to #2351: providing the attempt via fail2bans protocol (Pickle, client command, etc) must follow ignore facilities (shall be ignored if matches ignoreip, ignoreself, ignorecommand etc) --- fail2ban/server/filter.py | 5 ++++- fail2ban/tests/servertestcase.py | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/fail2ban/server/filter.py b/fail2ban/server/filter.py index 942e0ba1..44ed13b5 100644 --- a/fail2ban/server/filter.py +++ b/fail2ban/server/filter.py @@ -475,6 +475,10 @@ class Filter(JailThread): # Generate the failure attempt for the IP: unixTime = MyTime.time() ticket = FailTicket(ip, unixTime, matches=matches) + # check it shall be ignored: + if self._inIgnoreIPList(ip, ticket): + return 0 + # add attempt (found failure): logSys.info( "[%s] Attempt %s - %s", self.jailName, ip, datetime.datetime.fromtimestamp(unixTime).strftime("%Y-%m-%d %H:%M:%S") ) @@ -485,7 +489,6 @@ class Filter(JailThread): # 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.jail, ticket) - return 1 ## diff --git a/fail2ban/tests/servertestcase.py b/fail2ban/tests/servertestcase.py index 9f8a4cd3..e68dd3f5 100644 --- a/fail2ban/tests/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -393,6 +393,13 @@ class Transmitter(TransmitterBase): # resulted to ban for "192.0.2.2" but not for "192.0.2.1": self.assertLogged("Ban 192.0.2.2", wait=True) self.assertNotLogged("Ban 192.0.2.1") + # check attempt will be ignored by ignore facilities: + ip = "192.0.2.1" + self.transm.proceed(["set", self.jailName, "addignoreip", ip]) + self.assertLogged("Add %r to ignore list" % (ip,), wait=True) + self.assertEqual(attempt(ip, ["test failure %d" % i for i in (3,4,5)]), (0, 0)) + self.assertLogged("Ignore %s by ip" % (ip,), wait=True) + self.assertNotLogged("Ban 192.0.2.1") @with_alt_time def testJailBanList(self): From 6ac181f559afa763f744a773becfeef02d6c8239 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 1 Sep 2025 18:03:09 +0200 Subject: [PATCH 35/87] improve logging of date pattern (count of default templates added, info if it's filtered or used pre-match) --- fail2ban/server/datedetector.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/fail2ban/server/datedetector.py b/fail2ban/server/datedetector.py index b90e1b26..f89dc251 100644 --- a/fail2ban/server/datedetector.py +++ b/fail2ban/server/datedetector.py @@ -252,6 +252,8 @@ class DateDetector(object): "There is already a template with name %s" % name) self.__known_names.add(name) self.__templates.append(DateDetectorTemplate(template)) + logSys.debug(" date pattern regex for `%s`: `%s`", + getattr(template, 'pattern', ''), template.regex) def appendTemplate(self, template): """Add a date template to manage and use in search of dates. @@ -290,15 +292,14 @@ class DateDetector(object): DD_patternCache.set(key, template) self._appendTemplate(template) - logSys.info(" date pattern `%r`: `%s`", + logSys.info(" date pattern `%s`: `%s`", getattr(template, 'pattern', ''), template.name) - logSys.debug(" date pattern regex for %r: %s", - getattr(template, 'pattern', ''), template.regex) def addDefaultTemplate(self, filterTemplate=None, preMatch=None, allDefaults=True): """Add Fail2Ban's default set of date templates. """ ignoreDup = len(self.__templates) > 0 + cnt = 0 for template in ( DateDetector._defCache.templates if allDefaults else DateDetector._defCache.defaultTemplates ): @@ -311,6 +312,11 @@ class DateDetector(object): wrap=lambda s: RE_DATE_PREMATCH.sub(lambda m: DateTemplate.unboundPattern(s), preMatch)) # append date detector template (ignore duplicate if some was added before default): self._appendTemplate(template, ignoreDup=ignoreDup) + cnt += 1 + if preMatch: + logSys.info(" default date pattern for `%r`: %d template(s)", preMatch, cnt) + else: + logSys.info(" default %sdate pattern: %d template(s)", "filtered " if filterTemplate else "", cnt) @property def templates(self): From c54d505dea7c18608481bf481cda88cbf908b61c Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 1 Sep 2025 18:10:43 +0200 Subject: [PATCH 36/87] small amend (info with date pattern before debug message with regex) --- fail2ban/server/datedetector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fail2ban/server/datedetector.py b/fail2ban/server/datedetector.py index f89dc251..fd94d55f 100644 --- a/fail2ban/server/datedetector.py +++ b/fail2ban/server/datedetector.py @@ -291,9 +291,9 @@ class DateDetector(object): DD_patternCache.set(key, template) - self._appendTemplate(template) logSys.info(" date pattern `%s`: `%s`", getattr(template, 'pattern', ''), template.name) + self._appendTemplate(template) def addDefaultTemplate(self, filterTemplate=None, preMatch=None, allDefaults=True): """Add Fail2Ban's default set of date templates. From 912e3c81a2c56aa741cac9602e681614dcfc1041 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 1 Sep 2025 20:12:07 +0200 Subject: [PATCH 37/87] removes mistaken return in quiet case for set jail attempt command --- fail2ban/server/transmitter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/fail2ban/server/transmitter.py b/fail2ban/server/transmitter.py index 92d591f0..78afd9d6 100644 --- a/fail2ban/server/transmitter.py +++ b/fail2ban/server/transmitter.py @@ -353,7 +353,6 @@ class Transmitter: return self.__server.getBanTime(name) elif command[1] == "attempt": value = command[2:] - if self.__quiet: return return self.__server.addAttemptIP(name, *value) elif command[1].startswith("bantime."): value = command[2] From 9e72e78f3483c273a59fa04516093e46210d496b Mon Sep 17 00:00:00 2001 From: bill Date: Mon, 1 Sep 2025 22:34:53 -0400 Subject: [PATCH 38/87] filter.d/sendmail-reject.conf: support BSD log format. match user unknown messages. add aggressive mode for lost input channel and relaying denied messages --- ChangeLog | 5 +++++ config/filter.d/sendmail-reject.conf | 14 +++++++++----- fail2ban/tests/files/logs/sendmail-reject | 16 ++++++++++++++++ 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/ChangeLog b/ChangeLog index 62b74fba..3879d226 100644 --- a/ChangeLog +++ b/ChangeLog @@ -55,6 +55,11 @@ ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition - failregex extended to match different format of "Exit before auth" message (gh-3791) * `filter.d/recidive.conf` - restore possibility to set jail name in the filter, _jailname is positive now (gh-3769) * `filter.d/roundcube-auth.conf` - improved RE better matching log format of roundcube version 1.4+ (gh-3816) +* `filter.d/sendmail-reject.conf`: (gh-4020) + - support `` for BSD-style logfiles + - add match for `User unknown` to default + - the relay field may not always have a hostname before the ip address + - mode `aggressive` enables match for `lost input channel` and `Cannot resolve PTR record` * `filter.d/sshd.conf`: - adapted to conform possible new daemon name sshd-session, since OpenSSH 9.8 several log messages will be tagged with as originating from a process named "sshd-session" rather than "sshd" (gh-3782) diff --git a/config/filter.d/sendmail-reject.conf b/config/filter.d/sendmail-reject.conf index f969a060..7ff9a490 100644 --- a/config/filter.d/sendmail-reject.conf +++ b/config/filter.d/sendmail-reject.conf @@ -20,23 +20,25 @@ before = common.conf [Definition] _daemon = (?:(sm-(mta|acceptingconnections)|sendmail)) -__prefix_line = %(known/__prefix_line)s(?:\w{14,20}: )? -addr = (?:(?:IPv6:)?|) +__prefix_line = %(known/__prefix_line)s(?:\w{14,20}: )? +prefregex = ^%(__prefix_line)s.+$ -prefregex = ^%(__prefix_line)s.+$ +addr = (?:(?:IPv6:)?|) cmnfailre = ^ruleset=check_rcpt, arg1=(?P<\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+)|[45]5[13] [45]\.1\.8(?: (?P=email)\.\.\.)? Domain of sender address \S+ does not (?:exist|resolve)|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$ - ^from=<[^@]+@[^>]+>, size=\d+, class=\d+, nrcpts=\d+, bodytype=\w+, proto=E?SMTP, daemon=\S+, relay=\S+ \[%(addr)s\]$ + ^<[^@]+@[^>]+>\.\.\. (?:No such user here|User unknown)$ + ^from=<[^@]+@[^>]+>, size=\d+, class=\d+, nrcpts=\d+,(?: bodytype=\w+,)? proto=E?SMTP, daemon=\S+, relay=(?:\S+ )?\[%(addr)s\]$ mdre-normal = mdre-extra = ^(?:\S+ )?\[%(addr)s\](?: \(may be forged\))? did not issue \S+ during connection mdre-aggressive = %(mdre-extra)s + ^lost input channel from (?:\S+ )?\[%(addr)s\] to .*? after rcpt$ + ^ruleset=check_rcpt, arg1=(?P<\S+@\S+>), relay=(?:\S+ )?\[%(addr)s\](?: \(may be forged\))?, reject=(?:450 4\.4\.0(?: (?P=email)\.\.\.)?(?: Relaying temporarily denied\.)?(?: Cannot resolve PTR record for (\d+\.){3}\d+))$ failregex = %(cmnfailre)s > @@ -63,6 +65,8 @@ journalmatch = SYSLOG_IDENTIFIER=sm-mta + _SYSTEMD_UNIT=sendmail.service # Note the capture , includes both the __prefix_lines (which includes # the sendmail PID), but also the `\w{14}` which the the sendmail assigned # mail ID (todo: check this is necessary, possible obsolete). +# Avoid moving into the entire prefregex because the grouped messages we +# need have different syslog levels (info vs notice) that break the group if BSD verbose format is set # # Author: Daniel Black, Fabian Wenk and Sergey Brester aka sebres. # Rewritten using prefregex by Serg G. Brester. diff --git a/fail2ban/tests/files/logs/sendmail-reject b/fail2ban/tests/files/logs/sendmail-reject index c219578e..78cb8363 100644 --- a/fail2ban/tests/files/logs/sendmail-reject +++ b/fail2ban/tests/files/logs/sendmail-reject @@ -86,6 +86,8 @@ Nov 3 11:35:30 Microsoft sendmail[26254]: rA37ZTSC026251: Nov 3 11:35:30 Microsoft sendmail[26254]: rA37ZTSC026252: ... No such user here # failJSON: { "match": false } Nov 3 11:35:30 Microsoft sendmail[26254]: rA37ZTSC026252: ... No such user here +# failJSON: { "match": false, "desc": "Add User unknown" } +Jun 17 14:37:39 robin sm-mta[2794]: 55HIbcGI002794: ... User unknown # failJSON: { "match": false } Nov 3 11:35:30 Microsoft sendmail[26254]: rA37ZTSC026254: ... No such user here @@ -97,6 +99,11 @@ Nov 3 11:35:30 Microsoft sendmail[26254]: rA37ZTSC026252: ... # failJSON: { "match": false, "desc": "Different mail ID shouldn't match" } Nov 3 11:35:30 Microsoft sendmail[26254]: rA37ZTSC026255: from=, size=0, class=0, nrcpts=0, bodytype=8BITMIME, proto=ESMTP, daemon=MTA, relay=163.23.32.95.dsl-dynamic.vsi.ru [95.32.23.163] +# failJSON: { "time": "2005-06-17T14:37:39", "match": true, "host": "192.168.1.45", "desc": "BSD style log format with no hostname for the relay." } +Jun 17 14:37:39 robin sm-mta[2794]: 55HIbcGI002794: from=, size=108, class=0, nrcpts=0, proto=ESMTP, daemon=MTA-v4, relay=[192.168.1.45] + + + # filterOptions: {"mode": "extra"} # failJSON: { "time": "2005-03-06T16:55:28", "match": true , "host": "192.0.2.194", "desc": "wrong resp. non RFC compiant (ddos prelude?), MTA-mode" } @@ -115,3 +122,12 @@ Mar 29 22:51:43 server sendmail[3529565]: xA32R2PQ3529565: [192.0.2.2] did not i Mar 29 22:51:45 server sm-mta[50437]: 06QDQnNf050437: example.com [192.0.2.3] did not issue MAIL/EXPN/VRFY/ETRN during connection to IPv4 # failJSON: { "time": "2005-03-29T22:51:46", "match": true , "host": "2001:DB8::1", "desc": "IPv6" } Mar 29 22:51:46 server sm-mta[50438]: 06QDQnNf050438: example.com [IPv6:2001:DB8::1] did not issue MAIL/EXPN/VRFY/ETRN during connection to IPv6 + +# filterOptions: {"mode": "aggressive"} + +# failJSON: { "time": "2005-06-17T13:03:43", "match": true, "host": "127.0.0.1" } +Jun 17 13:03:43 robin sm-mta[26864]: 55HH324M026864: lost input channel from localhost [127.0.0.1] to MTA-v4 after rcpt + +# failJSON: { "time": "2005-06-18T08:05:17", "match": true, "host": "45.125.66.67" } +Jun 18 08:05:17 myhost sm-mta[17002]: 55IC59VD017002: ruleset=check_rcpt, arg1=, relay=[45.125.66.67], reject=450 4.4.0 ... Relaying temporarily denied. Cannot resolve PTR record for 45.125.66.67 + From 70d7fd0fddf7d6522a46de7eaa868f7183cbf5eb Mon Sep 17 00:00:00 2001 From: bill Date: Tue, 2 Sep 2025 12:54:42 -0400 Subject: [PATCH 39/87] update the test for lost input channel with real ip --- fail2ban/tests/files/logs/sendmail-reject | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fail2ban/tests/files/logs/sendmail-reject b/fail2ban/tests/files/logs/sendmail-reject index 78cb8363..0c7aa429 100644 --- a/fail2ban/tests/files/logs/sendmail-reject +++ b/fail2ban/tests/files/logs/sendmail-reject @@ -125,8 +125,8 @@ Mar 29 22:51:46 server sm-mta[50438]: 06QDQnNf050438: example.com [IPv6:2001:DB8 # filterOptions: {"mode": "aggressive"} -# failJSON: { "time": "2005-06-17T13:03:43", "match": true, "host": "127.0.0.1" } -Jun 17 13:03:43 robin sm-mta[26864]: 55HH324M026864: lost input channel from localhost [127.0.0.1] to MTA-v4 after rcpt +# failJSON: { "time": "2005-07-31T13:03:43", "match": true, "host": "139.162.120.196" } +Jul 31 13:03:43 alarmpi sm-mta[24291]: 56VFYCxS024291: lost input channel from mail.aqicn.org [139.162.120.196] to MTA-v4 after rcpt # failJSON: { "time": "2005-06-18T08:05:17", "match": true, "host": "45.125.66.67" } Jun 18 08:05:17 myhost sm-mta[17002]: 55IC59VD017002: ruleset=check_rcpt, arg1=, relay=[45.125.66.67], reject=450 4.4.0 ... Relaying temporarily denied. Cannot resolve PTR record for 45.125.66.67 From 13876e93ad513bb4f9a7e26032acd335e5388116 Mon Sep 17 00:00:00 2001 From: sebres Date: Tue, 2 Sep 2025 18:30:23 +0200 Subject: [PATCH 40/87] fixes the inconsistency with F-MLFID ("ID" matched by `(?:\w{14,20}: )?` is optional in message); simplify PR --- config/filter.d/sendmail-reject.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/filter.d/sendmail-reject.conf b/config/filter.d/sendmail-reject.conf index 7ff9a490..e8b2f9b4 100644 --- a/config/filter.d/sendmail-reject.conf +++ b/config/filter.d/sendmail-reject.conf @@ -20,11 +20,11 @@ before = common.conf [Definition] _daemon = (?:(sm-(mta|acceptingconnections)|sendmail)) -__prefix_line = %(known/__prefix_line)s(?:\w{14,20}: )? -prefregex = ^%(__prefix_line)s.+$ - +__prefix_line = %(known/__prefix_line)s(?:\w{14,20}: )? addr = (?:(?:IPv6:)?|) +prefregex = ^\s*(?:]+> )?%(__prefix_line)s.+$ + cmnfailre = ^ruleset=check_rcpt, arg1=(?P<\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+)|[45]5[13] [45]\.1\.8(?: (?P=email)\.\.\.)? Domain of sender address \S+ does not (?:exist|resolve)|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$ From 10b12e8c57ed3a10b5756285c4e277c699aeb21c Mon Sep 17 00:00:00 2001 From: sebres Date: Tue, 2 Sep 2025 18:36:22 +0200 Subject: [PATCH 41/87] reorder 2 tests belonging together --- fail2ban/tests/files/logs/sendmail-reject | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/fail2ban/tests/files/logs/sendmail-reject b/fail2ban/tests/files/logs/sendmail-reject index 0c7aa429..fbf03c6e 100644 --- a/fail2ban/tests/files/logs/sendmail-reject +++ b/fail2ban/tests/files/logs/sendmail-reject @@ -86,8 +86,6 @@ Nov 3 11:35:30 Microsoft sendmail[26254]: rA37ZTSC026251: Nov 3 11:35:30 Microsoft sendmail[26254]: rA37ZTSC026252: ... No such user here # failJSON: { "match": false } Nov 3 11:35:30 Microsoft sendmail[26254]: rA37ZTSC026252: ... No such user here -# failJSON: { "match": false, "desc": "Add User unknown" } -Jun 17 14:37:39 robin sm-mta[2794]: 55HIbcGI002794: ... User unknown # failJSON: { "match": false } Nov 3 11:35:30 Microsoft sendmail[26254]: rA37ZTSC026254: ... No such user here @@ -99,11 +97,11 @@ Nov 3 11:35:30 Microsoft sendmail[26254]: rA37ZTSC026252: ... # failJSON: { "match": false, "desc": "Different mail ID shouldn't match" } Nov 3 11:35:30 Microsoft sendmail[26254]: rA37ZTSC026255: from=, size=0, class=0, nrcpts=0, bodytype=8BITMIME, proto=ESMTP, daemon=MTA, relay=163.23.32.95.dsl-dynamic.vsi.ru [95.32.23.163] +# failJSON: { "match": false, "desc": "Add User unknown" } +Jun 17 14:37:39 robin sm-mta[2794]: 55HIbcGI002794: ... User unknown # failJSON: { "time": "2005-06-17T14:37:39", "match": true, "host": "192.168.1.45", "desc": "BSD style log format with no hostname for the relay." } Jun 17 14:37:39 robin sm-mta[2794]: 55HIbcGI002794: from=, size=108, class=0, nrcpts=0, proto=ESMTP, daemon=MTA-v4, relay=[192.168.1.45] - - # filterOptions: {"mode": "extra"} # failJSON: { "time": "2005-03-06T16:55:28", "match": true , "host": "192.0.2.194", "desc": "wrong resp. non RFC compiant (ddos prelude?), MTA-mode" } From 26b91862fcb8660f51711d00e8de42c5f046d0dc Mon Sep 17 00:00:00 2001 From: sebres Date: Tue, 2 Sep 2025 19:41:40 +0200 Subject: [PATCH 42/87] introduces a parameter `mta_dname` (default `\S+`) to allow more complex REs to match custom MTA daemon names (e.g. with spaces etc) --- config/filter.d/sendmail-reject.conf | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/config/filter.d/sendmail-reject.conf b/config/filter.d/sendmail-reject.conf index e8b2f9b4..cd7b51d1 100644 --- a/config/filter.d/sendmail-reject.conf +++ b/config/filter.d/sendmail-reject.conf @@ -22,6 +22,11 @@ before = common.conf _daemon = (?:(sm-(mta|acceptingconnections)|sendmail)) __prefix_line = %(known/__prefix_line)s(?:\w{14,20}: )? addr = (?:(?:IPv6:)?|) +# mta_dname -- matches name of MTA daemon (typically specified in DAEMON_OPTIONS), +# normally something without spaces like MTA-v4 or Deamon0, etc. If it'd contain spaces, one can +# rewrite it in jail using `filter = %(known/filter)s[mta_dname="[^,]+"]` or in .local overwrite +# of the filter. (we would not use catch-alls here to satisfy obscure artificial case). +mta_dname = \S+ prefregex = ^\s*(?:]+> )?%(__prefix_line)s.+$ @@ -30,14 +35,14 @@ cmnfailre = ^ruleset=check_rcpt, arg1=(?P<\S+@\S+>), relay=(\S+ )?\[%(add ^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|User unknown)$ - ^from=<[^@]+@[^>]+>, size=\d+, class=\d+, nrcpts=\d+,(?: bodytype=\w+,)? proto=E?SMTP, daemon=\S+, relay=(?:\S+ )?\[%(addr)s\]$ + ^from=<[^@]+@[^>]+>, size=\d+, class=\d+, nrcpts=\d+,(?: bodytype=\w+,)? proto=E?SMTP, daemon=%(mta_dname)s, relay=(?:\S+ )?\[%(addr)s\]$ mdre-normal = mdre-extra = ^(?:\S+ )?\[%(addr)s\](?: \(may be forged\))? did not issue \S+ during connection mdre-aggressive = %(mdre-extra)s - ^lost input channel from (?:\S+ )?\[%(addr)s\] to .*? after rcpt$ + ^lost input channel from (?:\S+ )?\[%(addr)s\] to %(mta_dname)s after rcpt$ ^ruleset=check_rcpt, arg1=(?P<\S+@\S+>), relay=(?:\S+ )?\[%(addr)s\](?: \(may be forged\))?, reject=(?:450 4\.4\.0(?: (?P=email)\.\.\.)?(?: Relaying temporarily denied\.)?(?: Cannot resolve PTR record for (\d+\.){3}\d+))$ failregex = %(cmnfailre)s From 3d23a44bb162534470974dd1209ae47e6a631a30 Mon Sep 17 00:00:00 2001 From: bill Date: Wed, 10 Sep 2025 13:27:30 -0400 Subject: [PATCH 43/87] fix `dig` to filter out warnings from email address capture --- config/action.d/xarf-login-attack.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/action.d/xarf-login-attack.conf b/config/action.d/xarf-login-attack.conf index f348b2c4..a0121966 100644 --- a/config/action.d/xarf-login-attack.conf +++ b/config/action.d/xarf-login-attack.conf @@ -44,7 +44,8 @@ actioncheck = actionban = oifs=${IFS}; RESOLVER_ADDR="%(addr_resolver)s" if [ "" -gt 0 ]; then echo "try to resolve $RESOLVER_ADDR"; fi - ADDRESSES=$(dig +short -t txt -q $RESOLVER_ADDR | tr -d '"') + ADDRESSES=$(dig +short -t txt -q $RESOLVER_ADDR | grep -v ';;' | tr -d '"') + if [ "" -gt 0 ]; then echo "returned address $ADDRESSES"; fi IFS=,; ADDRESSES=$(echo $ADDRESSES) IFS=${oifs} IP= From 85cfb8178263cf9a612a0e6996eb7095c1db27fd Mon Sep 17 00:00:00 2001 From: "Sergey G. Brester" Date: Wed, 10 Sep 2025 20:04:10 +0200 Subject: [PATCH 44/87] lets see an error (with debug messages) in debug case --- config/action.d/xarf-login-attack.conf | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/config/action.d/xarf-login-attack.conf b/config/action.d/xarf-login-attack.conf index a0121966..00da7f79 100644 --- a/config/action.d/xarf-login-attack.conf +++ b/config/action.d/xarf-login-attack.conf @@ -46,6 +46,11 @@ actionban = oifs=${IFS}; if [ "" -gt 0 ]; then echo "try to resolve $RESOLVER_ADDR"; fi ADDRESSES=$(dig +short -t txt -q $RESOLVER_ADDR | grep -v ';;' | tr -d '"') if [ "" -gt 0 ]; then echo "returned address $ADDRESSES"; fi + if [ -z "$ADDRESSES" ]; then + echo "address for $RESOLVER_ADDR cannot be found or timeout from dig"; + if [ "" -gt 0 ]; then exit 1; fi + exit 0 + fi IFS=,; ADDRESSES=$(echo $ADDRESSES) IFS=${oifs} IP= @@ -56,13 +61,11 @@ actionban = oifs=${IFS}; TLP= PORT= DATE=`LC_ALL=C date --date=@
-_nft_get_handle_id = grep -oP '@\s+.*\s+\Khandle\s+(\d+)$' +_nft_get_handle_id = sed -nE 's/.*@\s+.*\s+\#\s*(handle\s+[0-9]+)$/\1/p' _nft_add_set = add set
\{ type \; \} <_nft_for_proto--iter> @@ -67,7 +67,7 @@ _nft_del_set = { %(_nft_list)s | %(_nft_get_handle_id)s; } | while read -r hdl; # Notes.: command executed after the stop in order to delete table (it checks that no sets are available): # Values: CMD # -_nft_shutdown_table = { list table
| grep -qP '^\s+set\s+'; } || { +_nft_shutdown_table = { list table
| grep -qE '^\s+set\s+'; } || { delete table
} diff --git a/fail2ban/tests/servertestcase.py b/fail2ban/tests/servertestcase.py index 040e9c03..cf7006f2 100644 --- a/fail2ban/tests/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -1362,10 +1362,10 @@ class ServerConfigReaderTests(LogCaptureTestCase): "`{ nft flush set inet f2b-table addr6-set-j-w-nft-mp 2> /dev/null; } || ", ), 'stop': ( - r"`{ nft -a list chain inet f2b-table f2b-chain | grep -oP '@addr-set-j-w-nft-mp\s+.*\s+\Khandle\s+(\d+)$'; } | while read -r hdl; do`", + r"`{ nft -a list chain inet f2b-table f2b-chain | sed -nE 's/.*@addr-set-j-w-nft-mp\s+.*\s+\#\s*(handle\s+[0-9]+)$/\1/p'; } | while read -r hdl; do`", r"`nft delete rule inet f2b-table f2b-chain $hdl; done`", r"`nft delete set inet f2b-table addr-set-j-w-nft-mp`", - r"`{ nft -a list chain inet f2b-table f2b-chain | grep -oP '@addr6-set-j-w-nft-mp\s+.*\s+\Khandle\s+(\d+)$'; } | while read -r hdl; do`", + r"`{ nft -a list chain inet f2b-table f2b-chain | sed -nE 's/.*@addr6-set-j-w-nft-mp\s+.*\s+\#\s*(handle\s+[0-9]+)$/\1/p'; } | while read -r hdl; do`", r"`nft delete rule inet f2b-table f2b-chain $hdl; done`", r"`nft delete set inet f2b-table addr6-set-j-w-nft-mp`", ), @@ -1408,10 +1408,10 @@ class ServerConfigReaderTests(LogCaptureTestCase): "`{ nft flush set inet f2b-table addr6-set-j-w-nft-ap 2> /dev/null; } || ", ), 'stop': ( - r"`{ nft -a list chain inet f2b-table f2b-chain | grep -oP '@addr-set-j-w-nft-ap\s+.*\s+\Khandle\s+(\d+)$'; } | while read -r hdl; do`", + r"`{ nft -a list chain inet f2b-table f2b-chain | sed -nE 's/.*@addr-set-j-w-nft-ap\s+.*\s+\#\s*(handle\s+[0-9]+)$/\1/p'; } | while read -r hdl; do`", r"`nft delete rule inet f2b-table f2b-chain $hdl; done`", r"`nft delete set inet f2b-table addr-set-j-w-nft-ap`", - r"`{ nft -a list chain inet f2b-table f2b-chain | grep -oP '@addr6-set-j-w-nft-ap\s+.*\s+\Khandle\s+(\d+)$'; } | while read -r hdl; do`", + r"`{ nft -a list chain inet f2b-table f2b-chain | sed -nE 's/.*@addr6-set-j-w-nft-ap\s+.*\s+\#\s*(handle\s+[0-9]+)$/\1/p'; } | while read -r hdl; do`", r"`nft delete rule inet f2b-table f2b-chain $hdl; done`", r"`nft delete set inet f2b-table addr6-set-j-w-nft-ap`", ), From 9887ee441215a2a1f889b0e1baaa43cd3a956d26 Mon Sep 17 00:00:00 2001 From: sebres Date: Fri, 23 Jan 2026 21:28:52 +0100 Subject: [PATCH 72/87] CI: bump python version (3.15.0-alpha.5) --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 906c068c..ee6305c4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.8, 3.9, '3.10', '3.11', '3.12', '3.13', '3.14', '3.15.0-alpha.3', pypy3.11] + python-version: [3.8, 3.9, '3.10', '3.11', '3.12', '3.13', '3.14', '3.15.0-alpha.5', pypy3.11] fail-fast: false # Steps represent a sequence of tasks that will be executed as part of the job steps: From 6cdb5738ec2f840291157744e39f4878f319728b Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Wed, 28 Jan 2026 21:49:42 -0500 Subject: [PATCH 73/87] improved apprise fail2ban integration (support tagging) --- config/action.d/apprise.conf | 6 +++++- config/jail.conf | 13 +++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/config/action.d/apprise.conf b/config/action.d/apprise.conf index c6ce539a..3d80b2eb 100644 --- a/config/action.d/apprise.conf +++ b/config/action.d/apprise.conf @@ -45,5 +45,9 @@ actionunban = # Define location of the default apprise configuration file to use # config = /etc/fail2ban/apprise.conf + +# Support passing in arguments for example: "-g fail2ban" # -apprise = apprise -c "" +apprise_args = +# +apprise = apprise -c "" diff --git a/config/jail.conf b/config/jail.conf index d0b3fa44..47fa1c35 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -228,11 +228,16 @@ 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. +# See https://appriseit.com/services/ for details on what is supported. +# See https://appriseit.com/getting-started/configuration/ for information on how to prepare +# an Apprise configuration file. Both YAML and TEXT formats are 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. +# By default apprise attempts to load the configuration file found in +# /etc/fail2ban/apprise.conf unless you over-ride this. +# +# Leverage apprise_args to optionally identify tags (--tag ) entries +# apprise[config="/alternate/path/to/apprise.cfg", apprise_args='-g fail2ban'] +# apprise[config="/alternate/path/to/apprise.yaml", apprise_args='--tag fail2ban'] # action = %(action_)s # apprise From 8afd0c89560d5af2ab97507e19a9d18ec1cff23e Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Wed, 28 Jan 2026 21:55:04 -0500 Subject: [PATCH 74/87] updated ChangeLog to reflect Apprise updates --- ChangeLog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ChangeLog b/ChangeLog index ab0afcf3..d054c154 100644 --- a/ChangeLog +++ b/ChangeLog @@ -11,6 +11,9 @@ ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition ----------- ### Compatibility +* `action.d/apprise.conf` updated to support tagging and other command line +* `jail.conf` updated apprise inline reference/documentation +options * `action.d/iptables.conf` rewritten due to support of multiple chains (gh-3909), therefore user-level derivations (action including iptables-based action) may become incompatible, e. g. some tags if used need to be replaced, e. g. `` with `$chain` or `<_ipt_for_proto-iter>` with `<_ipt-iter>`; From 8a8afefd70bc98a42dbddbb3a3cd8e39948a4756 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sun, 1 Feb 2026 19:45:44 -0500 Subject: [PATCH 75/87] applied updates based on PR feedback --- config/action.d/apprise.conf | 67 ++++++++++++++++++++++++++++++++++-- config/jail.conf | 15 +++----- 2 files changed, 69 insertions(+), 13 deletions(-) diff --git a/config/action.d/apprise.conf b/config/action.d/apprise.conf index 3d80b2eb..e025da2f 100644 --- a/config/action.d/apprise.conf +++ b/config/action.d/apprise.conf @@ -2,8 +2,69 @@ # # Author: Chris Caron # +# ban & send a notification to one or more of the 120+ services supported by +# Apprise. +# - See https://appriseit.com/services/ for details on what is supported. +# - See https://appriseit.com/getting-started/configuration/ for information +# on how to prepare an Apprise configuration file. # - +# This plugin requires that Apprise is installed on your system: +# +# pip install apprise +# +# Breakdown: +# config provide a path to an Apprise Config file +# Thie default is /etc/fail2ban/apprise.conf if not provided. +# Both YAML and TEXT formats are supported. +# You can even point your configuration to an Apprise API +# endpoint. +# +# args Provide additional arguments to support the Apprise CLI. +# See https://appriseit.com/cli/usage/ for additional options. +# the --tag (-g) is incredibly useful for integrating with +# fail2ban as you can exculsively have it target specific +# notifications this way. +# +# Config Example #1: Simple +# 1. Create a /etc/fail2ban/apprise.conf +# ``` +# # /etc/fail2ban/apprise.conf +# fail2ban=mailto://user:pass@example.com +# ``` +# 2 In /etc/fail2ban/jail.conf +# ``` +# # ... +# apprise[args='--tag fail2ban'] +# action = %(action_)s +# apprise +# # ... +# ``` +# +# Config Example #2: YAML an Custom path +# 1. Create a /etc/fail2ban/apprise.conf +# ``` +# # /etc/fail2ban/apprise.yaml +# urls: +# - mailto://user:pass@example.com: +# tags: f2b +# ``` +# 2. In /etc/fail2ban/jail.conf +# ``` +# # ... +# apprise[config='/etc/fail2ban/apprise.yaml',args='--tag f2b'] +# action = %(action_)s +# apprise +# # ... +# ``` +# +# Config Example #3: Apprise API +# 1. in /etc/fail2ban/jail.conf +# ``` +# # ... +# apprise[config='http://apprise.example.ca/get/mykey',args='-g f2b'] +# action = %(action_)s +# apprise +# # ... [Definition] # Option: actionstart @@ -48,6 +109,6 @@ config = /etc/fail2ban/apprise.conf # Support passing in arguments for example: "-g fail2ban" # -apprise_args = +args = # -apprise = apprise -c "" +apprise = apprise -c "" diff --git a/config/jail.conf b/config/jail.conf index 47fa1c35..d53de584 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -227,17 +227,12 @@ 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://appriseit.com/services/ for details on what is supported. -# See https://appriseit.com/getting-started/configuration/ for information on how to prepare -# an Apprise configuration file. Both YAML and TEXT formats are supported +# Apprise Integration # -# By default apprise attempts to load the configuration file found in -# /etc/fail2ban/apprise.conf unless you over-ride this. -# -# Leverage apprise_args to optionally identify tags (--tag ) entries -# apprise[config="/alternate/path/to/apprise.cfg", apprise_args='-g fail2ban'] -# apprise[config="/alternate/path/to/apprise.yaml", apprise_args='--tag fail2ban'] +# Leverage args to optionally identify tags (--tag ) entries +# - See action.d/apprise.conf for more details in your current installation +# - config= default is /etc/fail2ban/apprise.conf unless you over-ride it. +# apprise[args='--tag fail2ban'] # action = %(action_)s # apprise From 36e28359ed907ba720611db4c89c0b6da02293f3 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sun, 1 Feb 2026 19:51:26 -0500 Subject: [PATCH 76/87] fixed spelling --- config/action.d/apprise.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/action.d/apprise.conf b/config/action.d/apprise.conf index e025da2f..783c740b 100644 --- a/config/action.d/apprise.conf +++ b/config/action.d/apprise.conf @@ -14,7 +14,7 @@ # # Breakdown: # config provide a path to an Apprise Config file -# Thie default is /etc/fail2ban/apprise.conf if not provided. +# The default is /etc/fail2ban/apprise.conf if not provided. # Both YAML and TEXT formats are supported. # You can even point your configuration to an Apprise API # endpoint. @@ -22,7 +22,7 @@ # args Provide additional arguments to support the Apprise CLI. # See https://appriseit.com/cli/usage/ for additional options. # the --tag (-g) is incredibly useful for integrating with -# fail2ban as you can exculsively have it target specific +# fail2ban as you can exclusively have it target specific # notifications this way. # # Config Example #1: Simple From 1a802bee93a4aa942d3869784b2f81a4964e1a1f Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sun, 1 Feb 2026 20:18:07 -0500 Subject: [PATCH 77/87] further feedback from PR --- config/jail.conf | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/config/jail.conf b/config/jail.conf index d53de584..aa2da268 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -227,14 +227,11 @@ action_mwl = %(action_)s action_xarf = %(action_)s xarf-login-attack[service=%(__name__)s, sender="%(sender)s", logpath="%(logpath)s", port="%(port)s"] -# Apprise Integration -# -# Leverage args to optionally identify tags (--tag ) entries -# - See action.d/apprise.conf for more details in your current installation -# - config= default is /etc/fail2ban/apprise.conf unless you over-ride it. -# apprise[args='--tag fail2ban'] +# ban & send a notification to one or more of the 120+ services supported by Apprise. # action = %(action_)s -# apprise +# apprise[config="/alternate/path/to/apprise.yaml", args='--tag fail2ban'] +# See https://github.com/caronc/apprise/wiki for details on what is supported. +# Or action.d/apprise.conf for more details how to configure or customize it. # ban IP on CloudFlare & send an e-mail with whois report and relevant log lines # to the destemail. From f457cf81319f80d2be0f55dc32551e095a4c73ac Mon Sep 17 00:00:00 2001 From: "Sergey G. Brester" Date: Mon, 2 Feb 2026 02:31:19 +0100 Subject: [PATCH 78/87] ChangeLog adjusted move from compat to enhancement section --- ChangeLog | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index d054c154..abb9a424 100644 --- a/ChangeLog +++ b/ChangeLog @@ -11,9 +11,6 @@ ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition ----------- ### Compatibility -* `action.d/apprise.conf` updated to support tagging and other command line -* `jail.conf` updated apprise inline reference/documentation -options * `action.d/iptables.conf` rewritten due to support of multiple chains (gh-3909), therefore user-level derivations (action including iptables-based action) may become incompatible, e. g. some tags if used need to be replaced, e. g. `` with `$chain` or `<_ipt_for_proto-iter>` with `<_ipt-iter>`; @@ -101,6 +98,7 @@ options by first ban (and automatically reloaded by update after small latency to avoid expensive stats check on every compare); the entries inside the file can be separated by comma, space or new line with optional comments (text following chars `#` or `;` after space or newline would be ignored up to next newline) +* `action.d/apprise.conf` - updated to support tagging and other command line args (gh-4141) * `action.d/*-ipset.conf`: - parameter `ipsettype` to set type of ipset, e. g. hash:ip, hash:net, etc (gh-3760) * `action.d/iptables.conf` - action and few derivatives of it extended to handle multiple chains, From 025adbf48598b5fa014c73e4e158caa58efb3c87 Mon Sep 17 00:00:00 2001 From: "Sergey G. Brester" Date: Mon, 2 Feb 2026 02:37:26 +0100 Subject: [PATCH 79/87] fixes apprise action configuration examples --- config/action.d/apprise.conf | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/config/action.d/apprise.conf b/config/action.d/apprise.conf index 783c740b..4b8708a3 100644 --- a/config/action.d/apprise.conf +++ b/config/action.d/apprise.conf @@ -34,9 +34,8 @@ # 2 In /etc/fail2ban/jail.conf # ``` # # ... -# apprise[args='--tag fail2ban'] # action = %(action_)s -# apprise +# apprise[args='--tag fail2ban'] # # ... # ``` # @@ -51,9 +50,8 @@ # 2. In /etc/fail2ban/jail.conf # ``` # # ... -# apprise[config='/etc/fail2ban/apprise.yaml',args='--tag f2b'] # action = %(action_)s -# apprise +# apprise[config='/etc/fail2ban/apprise.yaml',args='--tag f2b'] # # ... # ``` # @@ -61,9 +59,8 @@ # 1. in /etc/fail2ban/jail.conf # ``` # # ... -# apprise[config='http://apprise.example.ca/get/mykey',args='-g f2b'] # action = %(action_)s -# apprise +# apprise[config='http://apprise.example.ca/get/mykey',args='-g f2b'] # # ... [Definition] From 05f6ad4fcc337a9cf6a4f53d00f8f867a97c6086 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sun, 1 Feb 2026 20:52:06 -0500 Subject: [PATCH 80/87] small fix to url for Apprise doc source --- config/jail.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/jail.conf b/config/jail.conf index aa2da268..18749e1b 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -230,7 +230,7 @@ action_xarf = %(action_)s # ban & send a notification to one or more of the 120+ services supported by Apprise. # action = %(action_)s # apprise[config="/alternate/path/to/apprise.yaml", args='--tag fail2ban'] -# See https://github.com/caronc/apprise/wiki for details on what is supported. +# See https://appriseit.com/services/ for details on what is supported. # Or action.d/apprise.conf for more details how to configure or customize it. # ban IP on CloudFlare & send an e-mail with whois report and relevant log lines From 3bead7c011aa5c695586203349599fa32f9d7fa9 Mon Sep 17 00:00:00 2001 From: "Sergey G. Brester" Date: Mon, 2 Feb 2026 03:04:25 +0100 Subject: [PATCH 81/87] Update comments in action jail.conf shall be unmodified (jails are ideally in jail.local or jail.d/*.conf) --- config/action.d/apprise.conf | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/config/action.d/apprise.conf b/config/action.d/apprise.conf index 4b8708a3..899b1e5f 100644 --- a/config/action.d/apprise.conf +++ b/config/action.d/apprise.conf @@ -31,12 +31,10 @@ # # /etc/fail2ban/apprise.conf # fail2ban=mailto://user:pass@example.com # ``` -# 2 In /etc/fail2ban/jail.conf +# 2 In jail: # ``` -# # ... # action = %(action_)s # apprise[args='--tag fail2ban'] -# # ... # ``` # # Config Example #2: YAML an Custom path @@ -47,21 +45,19 @@ # - mailto://user:pass@example.com: # tags: f2b # ``` -# 2. In /etc/fail2ban/jail.conf +# 2. In jail: # ``` -# # ... # action = %(action_)s # apprise[config='/etc/fail2ban/apprise.yaml',args='--tag f2b'] -# # ... # ``` # # Config Example #3: Apprise API -# 1. in /etc/fail2ban/jail.conf +# 1. In jail: # ``` -# # ... # action = %(action_)s # apprise[config='http://apprise.example.ca/get/mykey',args='-g f2b'] -# # ... +# ``` + [Definition] # Option: actionstart From eb7ed973ef802487fcb4fb6dc537951895845b61 Mon Sep 17 00:00:00 2001 From: sebres Date: Thu, 12 Feb 2026 13:53:57 +0100 Subject: [PATCH 82/87] `filter.d/nginx-http-auth.conf`: modes `fallback` and `aggressive` extended to match more SSL failures, see gh-4142 (amend to gh-2881) --- config/filter.d/nginx-http-auth.conf | 2 +- fail2ban/tests/files/logs/nginx-http-auth | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/config/filter.d/nginx-http-auth.conf b/config/filter.d/nginx-http-auth.conf index ff59f4e3..05a77415 100644 --- a/config/filter.d/nginx-http-auth.conf +++ b/config/filter.d/nginx-http-auth.conf @@ -16,7 +16,7 @@ prefregex = ^%(__prefix_line)s.*%(__suffix_line)s\s*$ mdre-auth = ^user "(?:[^"]+|.*?)":? (?:password mismatch|was not found in "[^\"]*")$ ^(?:PAM: )?user '(?:[^']+|.*?)' - not authenticated: Authentication failure$ -mdre-fallback = ^SSL_do_handshake\(\) failed \(SSL: error:\S+(?: \S+){1,3} too (?:long|short)\)[^,]* +mdre-fallback = ^SSL_(?:do_handshake|read)\(\) failed \(SSL: error:\S+(?: \S+){1,3}[^\)]*\)[^,]* mdre-normal = %(mdre-auth)s diff --git a/fail2ban/tests/files/logs/nginx-http-auth b/fail2ban/tests/files/logs/nginx-http-auth index d7595b1a..24cb3922 100644 --- a/fail2ban/tests/files/logs/nginx-http-auth +++ b/fail2ban/tests/files/logs/nginx-http-auth @@ -35,6 +35,13 @@ host nginx[23141]: host nginx: 2025/09/07 21:20:40 [error] 23141#23141: *24470 P # failJSON: { "time": "2020-11-25T16:02:45", "match": false } 2020/11/25 16:02:45 [error] 76952#76952: *5645766 connect() failed (111: Connection refused) while connecting to upstream, client: 5.126.32.148, server: www.google.de, request: "GET /admin/config HTTP/2.0", upstream: "http://127.0.0.1:3000/admin/config", host: "www.google.de" +# failJSON: { "time": "2025-07-09T19:20:38", "match": true , "host": "192.0.2.23", "desc": "SSL failure: bad key share (gh-4142)" } +2025/07/09 19:20:38 [crit] 3075615#3075615: *2489 SSL_do_handshake() failed (SSL: error:0A00006C:SSL routines::bad key share) while SSL handshaking, client: 192.0.2.23, server: 0.0.0.0:443 +# failJSON: { "time": "2025-07-09T20:18:52", "match": true , "host": "192.0.2.24", "desc": "SSL failure: bad record type (gh-4142)" } +2025/07/09 20:18:52 [crit] 60993#60993: *16611546 SSL_do_handshake() failed (SSL: error:0A0001BB:SSL routines::bad record type error:0A000139:SSL routines::record layer failure) while SSL handshaking, client: 192.0.2.24, server: 0.0.0.0:443 +# failJSON: { "time": "2025-07-09T22:27:36", "match": true , "host": "192.0.2.17", "desc": "SSL failure: alert number 121 (gh-4142)" } +2025/07/09 22:27:36 [crit] 60993#60993: *16700609 SSL_read() failed (SSL: error:0A000461:SSL routines::reason(1121):SSL alert number 121) while waiting for request, client: 192.0.2.17, server: 0.0.0.0:443 + # filterOptions: [{"mode": "aggressive"}] # failJSON: { "time": "2020-11-25T14:42:16", "match": true , "host": "142.93.180.14" } 2020/11/25 14:42:16 [crit] 76952#76952: *2454307 SSL_do_handshake() failed (SSL: error:1408F0C6:SSL routines:ssl3_get_record:packet length too long) while SSL handshaking, client: 142.93.180.14, server: 0.0.0.0:443 From c03a6204c1cdec5f8c284c2a5866698f6fdd7c34 Mon Sep 17 00:00:00 2001 From: sebres Date: Thu, 12 Feb 2026 14:03:07 +0100 Subject: [PATCH 83/87] ChangeLog update --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index abb9a424..a87cc00f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -115,6 +115,7 @@ ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition * `filter.d/nginx-http-auth.conf`: - extended with `prefregex` to capture content of error only (bypass common prefix and suffix, like server, request, host, referrer); - extended to match PAM authentication failures (gh-4071) + - modes `fallback` and `aggressive` extended to match more SSL failures by SSL_do_handshake or SSL_read (gh-4142, gh-2881) * `filter.d/nginx-limit-req.conf` - extended to ban hosts failed by limit connection in ngx_http_limit_conn_module (gh-3674, gh-4047) * `filter.d/proxmox.conf` - add support to Proxmox Web GUI (gh-2966) * `filter.d/openvpn.conf` - new filter and jail for openvpn recognizing failed TLS handshakes (gh-2702) From 0a8356dd31bedb49c1cd3de0d5e0b3541c3530c1 Mon Sep 17 00:00:00 2001 From: "Sergey G. Brester" Date: Thu, 26 Mar 2026 21:03:29 +0100 Subject: [PATCH 84/87] `jail.conf`: removed dead link in warning comment removed dead URL (reference to 690-fail2ban-+-dns-fail.html in blog) from the warning comment about UDP spoofing; closes gh-4162. --- config/jail.conf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/jail.conf b/config/jail.conf index 18749e1b..bf714f0d 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -721,8 +721,7 @@ backend = %(syslog_backend)s # Since UDP is connection-less protocol, spoofing of IP and imitation # of illegal actions is way too simple. Thus enabling of this filter # might provide an easy way for implementing a DoS against a chosen -# victim. See -# http://nion.modprobe.de/blog/archives/690-fail2ban-+-dns-fail.html +# victim. # Please DO NOT USE this jail unless you know what you are doing. # # IMPORTANT: see filter.d/named-refused for instructions to enable logging From 732dc86ef33bd32f4409088edcf79218ff0753b2 Mon Sep 17 00:00:00 2001 From: "Sergey G. Brester" Date: Fri, 10 Apr 2026 16:44:43 +0200 Subject: [PATCH 85/87] simplify timestamp conversion in systemd journal reader; also handle DST flag correct, so doesn't mistakenly consider the UTC timestamp as local timestamp, see #2882 --- fail2ban/server/filtersystemd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fail2ban/server/filtersystemd.py b/fail2ban/server/filtersystemd.py index 3e261417..6bb9d466 100644 --- a/fail2ban/server/filtersystemd.py +++ b/fail2ban/server/filtersystemd.py @@ -301,7 +301,7 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover date = logentry.get('_SOURCE_REALTIME_TIMESTAMP') if date is None: date = logentry.get('__REALTIME_TIMESTAMP') - return (date.isoformat(), time.mktime(date.timetuple()) + date.microsecond/1.0E6) + return (date.isoformat(), date.timestamp()) ## # Format journal log entry into syslog style From 507d0468cdae84e1280faf3b0d3b6b4a800d0aa0 Mon Sep 17 00:00:00 2001 From: sebres Date: Sat, 11 Apr 2026 14:25:12 +0200 Subject: [PATCH 86/87] implements RFE #4164: new tag `` available in `ignorecommand` or actions (same as `` there) --- fail2ban/server/actions.py | 1 + fail2ban/tests/filtertestcase.py | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/fail2ban/server/actions.py b/fail2ban/server/actions.py index 581c5724..6b603710 100644 --- a/fail2ban/server/actions.py +++ b/fail2ban/server/actions.py @@ -394,6 +394,7 @@ class Actions(JailThread, Mapping): # raw ticket info: "raw-ticket": lambda self: repr(self.__ticket), # jail info: + "jail.name": lambda self: self.__jail.name, "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(), diff --git a/fail2ban/tests/filtertestcase.py b/fail2ban/tests/filtertestcase.py index ef25c50a..65a609b7 100644 --- a/fail2ban/tests/filtertestcase.py +++ b/fail2ban/tests/filtertestcase.py @@ -617,14 +617,14 @@ class IgnoreIP(LogCaptureTestCase): self.pruneLog() self.assertFalse(self.filter.inIgnoreIPList(FailTicket("2001:db8::ffff"))) self.assertLogged("returned successfully 1") - # by user-name (ignore tester): - self.filter.ignoreCommand = 'if [ "" = "tester" ]; then exit 0; fi; exit 1' + # by user-name (ignore tester), also test jail.name tag: + self.filter.ignoreCommand = 'echo "jail:"; if [ "" = "tester" ]; then exit 0; fi; exit 1' self.pruneLog() self.assertTrue(self.filter.inIgnoreIPList(FailTicket("tester", data={'user': 'tester'}))) - self.assertLogged("returned successfully 0") + self.assertLogged("stdout: %r" % 'jail:DummyJail', "returned successfully 0", all=True) self.pruneLog() self.assertFalse(self.filter.inIgnoreIPList(FailTicket("root", data={'user': 'root'}))) - self.assertLogged("returned successfully 1", all=True) + self.assertLogged("stdout: %r" % 'jail:DummyJail', "returned successfully 1", all=True) def testIgnoreCache(self): # like both test-cases above, just cached (so once per key)... From 8d3f5048ef439befd51f8b0fe384850c37fd68cc Mon Sep 17 00:00:00 2001 From: sebres Date: Sat, 11 Apr 2026 14:42:57 +0200 Subject: [PATCH 87/87] `filter.d/postfix.conf` - extended `prefregex` to capture username in postfix SASL failures; closes gh-4165 --- ChangeLog | 1 + config/filter.d/postfix.conf | 2 +- fail2ban/tests/files/logs/postfix | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index a87cc00f..216ffe88 100644 --- a/ChangeLog +++ b/ChangeLog @@ -59,6 +59,7 @@ ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition FreeSWITCH log line prefix has changed in newer versions (gh-3143) * `filter.d/lighttpd-auth.conf` - fixed regex (if failures generated by systemd-journal), bypass several prefixes now (gh-3955) * `filter.d/postfix.conf`: + - extended `prefregex` to capture username in postfix SASL failures (gh-4165) - consider CONNECT and other rejected commands as a valid `_pref` (gh-3800) - default `_daemon` in prefix-line is loosened - can match everything starting with word postfix, like `postfix-example.com/smtpd` (gh-3297) - add optional `NOQUEUE:` prefix to ddos regex (gh-4072) diff --git a/config/filter.d/postfix.conf b/config/filter.d/postfix.conf index a3a97c5c..305e2ded 100644 --- a/config/filter.d/postfix.conf +++ b/config/filter.d/postfix.conf @@ -18,7 +18,7 @@ _pref = (?:\w+: )? # SMTP commands like RCPT etc _cmd = [A-Z]{4,} -prefregex = ^%(__prefix_line)s%(_pref)s> .+$ +prefregex = ^%(__prefix_line)s%(_pref)s> .+?(?:, sasl_username=\S+)?\s*$ # 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) ; pragma: codespell-ignore diff --git a/fail2ban/tests/files/logs/postfix b/fail2ban/tests/files/logs/postfix index d775c2ed..30a4df24 100644 --- a/fail2ban/tests/files/logs/postfix +++ b/fail2ban/tests/files/logs/postfix @@ -151,7 +151,7 @@ Jan 14 16:18:16 xxx postfix/smtpd[14933]: warning: host[192.0.2.5]: SASL CRAM-MD # failJSON: { "time": "2005-01-14T16:18:16", "match": true , "host": "192.0.2.5", "desc": "aggressive only" } Jan 14 16:18:16 xxx postfix/smtpd[14933]: warning: host[192.0.2.5]: SASL CRAM-MD5 authentication failed: Invalid authentication mechanism -# failJSON: { "time": "2004-11-04T09:11:01", "match": true , "host": "192.0.2.152", "desc": "reason unavailable" } +# failJSON: { "time": "2004-11-04T09:11:01", "match": true , "host": "192.0.2.152", "user": "admin", "desc": "reason unavailable" } Nov 4 09:11:01 mail postfix/smtpd[1234]: warning: unknown[192.0.2.152]: SASL LOGIN authentication failed: (reason unavailable), sasl_username=admin # ---------------------------------------