Merge branch 'fail2ban:master' into master

This commit is contained in:
Dream 2026-09-30 04:10:37 +08:00 • committed by GitHub
commit c6185116a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 392 additions and 106 deletions

View file

@ -17,6 +17,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v7
- name: Codespell
uses: codespell-project/actions-codespell@v2

View file

@ -22,15 +22,15 @@ 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.5', pypy3.11]
python-version: [3.8, 3.9, '3.10', '3.11', '3.12', '3.13', '3.14', '3.15.0-rc.2', pypy3.11]
fail-fast: false
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}

View file

@ -7,7 +7,24 @@
Fail2Ban: Changelog
===================
ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition
ver. 1.1.2-dev-1 (20??/??/??) - development nightly edition
-----------
### Fixes
* `filter.d/nginx-botsearch.conf` - error-log regex extended with support of journal format (gh-3732)
* `filter.d/apache-noscript.conf` - match a request for the `cgi-bin` directory itself (no script name after it),
the `cgi-bin` alternative required a following path segment so `/cgi-bin/` and `/cgi-bin` were not matched (gh-4217)
* sockets passed via `systemd` socket activation (`sd_listen_fds(3)`) are not shut down or removed anymore (gh-4225)
* `paths-*.conf`: add default banactions for several includes depending on default net-filter for distribution (gh-4224, gh-4229):
`paths-arch.conf`, `paths-fedora.conf`, `paths-opensuse.conf` - nftables, `paths-freebsd.conf`, `paths-osx.conf ` - pf;
### New Features and Enhancements
* `filter.d/cowrie.conf` - new filter and jail for Cowrie SSH/Telnet honeypot JSON log output (gh-4216)
* `filter.d/gitlab.conf` - `logtype` option `json` adding support for the `application_json.log` format that
superseded the deprecated `application.log`; default type `file` keeps the previous behaviour (gh-3566, gh-4230)
ver. 1.1.1 (2026/08/15) - triple-one-win
-----------
### Compatibility
@ -18,6 +35,7 @@ ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition
to use it as before set `mode = more` for exim jail, but be aware of the consequences.
### Fixes
* fixes catastrophic backtracking explosion for REs in domino-smtp and dovecot filters (GHSA-33wh-ccjc-p397, gh-4221)
* fixes `systemd` bug with missing journal descriptor after rotation by reopening of journal if it is recognized as not alive (gh-3929)
* improve threaded clean-up of all filters, new thread functions `afterStop` (to force clean-up after stop) and `done`, invoking `afterStop` once
* ensure journal-reader is always closed (additional prevention against leaks and "too many open files"), thereby avoid sporadic segfault
@ -65,6 +83,7 @@ ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition
- add optional `NOQUEUE:` prefix to ddos regex (gh-4072)
- internal parameter `_pref` is renamed to `_cmd`, `_pref` matches now optional prefix like `NOQUEUE: ` etc
- modes `ddos` and `aggressive` extended to match `rate limit exceeded` for connection or message delivery request rates (gh-3265, gh-4073)
- modes `aggressive` extended to match `hostname does not resolve to address` (gh-4218, gh-4078)
* `filter.d/dropbear.conf`:
- recognizes extra pid/timestamp if logged into stdout/journal, added `journalmatch` (gh-3597)
- failregex extended to match different format of "Exit before auth" message (gh-3791)
@ -83,6 +102,7 @@ ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition
* `filter.d/froxlor-auth.conf` - updated the regex to the new logging situation for froxlor and changed logpath in jail.conf (gh-4075).
### New Features and Enhancements
* `fail2ban-server` supports `systemd` socket activation (`sd_listen_fds(3)`); optional `fail2ban.socket` unit provided
* backend `systemd` extended with new parameter `rotated` (default `false`, as prevention against "too many open files"),
that allows to monitor only actual journals and ignore now a lot of rotated files by default; so can drastically reduce
amount of used file descriptors, normally to 1 or 2 descriptors per jail (gh-3391)

View file

@ -11,6 +11,7 @@ config/action.d/bsd-ipfw.conf
config/action.d/cloudflare.conf
config/action.d/cloudflare-token.conf
config/action.d/complain.conf
config/action.d/csf.conf
config/action.d/dshield.conf
config/action.d/dummy.conf
config/action.d/firewallcmd-allports.conf
@ -129,6 +130,7 @@ config/filter.d/nginx-http-auth.conf
config/filter.d/nginx-limit-req.conf
config/filter.d/nsd.conf
config/filter.d/openhab.conf
config/filter.d/openvpn.conf
config/filter.d/openwebmail.conf
config/filter.d/oracleims.conf
config/filter.d/pam-generic.conf
@ -138,6 +140,7 @@ config/filter.d/php-url-fopen.conf
config/filter.d/portsentry.conf
config/filter.d/postfix.conf
config/filter.d/proftpd.conf
config/filter.d/proxmox.conf
config/filter.d/pure-ftpd.conf
config/filter.d/qmail.conf
config/filter.d/recidive.conf
@ -162,10 +165,12 @@ config/filter.d/suhosin.conf
config/filter.d/tine20.conf
config/filter.d/traefik-auth.conf
config/filter.d/uwimap-auth.conf
config/filter.d/vaultwarden.conf
config/filter.d/vsftpd.conf
config/filter.d/webmin-auth.conf
config/filter.d/wuftpd.conf
config/filter.d/xinetd-fail.conf
config/filter.d/xrdp.conf
config/filter.d/znc-adminlog.conf
config/filter.d/zoneminder.conf
config/jail.conf
@ -340,6 +345,7 @@ fail2ban/tests/files/logs/nginx-http-auth
fail2ban/tests/files/logs/nginx-limit-req
fail2ban/tests/files/logs/nsd
fail2ban/tests/files/logs/openhab
fail2ban/tests/files/logs/openvpn
fail2ban/tests/files/logs/openwebmail
fail2ban/tests/files/logs/oracleims
fail2ban/tests/files/logs/pam-generic
@ -349,6 +355,7 @@ fail2ban/tests/files/logs/php-url-fopen
fail2ban/tests/files/logs/portsentry
fail2ban/tests/files/logs/postfix
fail2ban/tests/files/logs/proftpd
fail2ban/tests/files/logs/proxmox
fail2ban/tests/files/logs/pure-ftpd
fail2ban/tests/files/logs/qmail
fail2ban/tests/files/logs/recidive
@ -373,10 +380,12 @@ fail2ban/tests/files/logs/suhosin
fail2ban/tests/files/logs/tine20
fail2ban/tests/files/logs/traefik-auth
fail2ban/tests/files/logs/uwimap-auth
fail2ban/tests/files/logs/vaultwarden
fail2ban/tests/files/logs/vsftpd
fail2ban/tests/files/logs/webmin-auth
fail2ban/tests/files/logs/wuftpd
fail2ban/tests/files/logs/xinetd-fail
fail2ban/tests/files/logs/xrdp
fail2ban/tests/files/logs/znc-adminlog
fail2ban/tests/files/logs/zoneminder
fail2ban/tests/files/logs/zzz-generic-example
@ -390,6 +399,7 @@ fail2ban/tests/files/testcase-journal.log
fail2ban/tests/files/testcase-multiline.log
fail2ban/tests/files/testcase-usedns.log
fail2ban/tests/files/testcase-wrong-char.log
fail2ban/tests/files/test-ign-ips-file
fail2ban/tests/files/zzz-sshd-obsolete-multiline.log
fail2ban/tests/filtertestcase.py
fail2ban/tests/__init__.py
@ -410,7 +420,7 @@ files/fail2ban-logrotate
files/fail2ban-openrc.conf
files/fail2ban-openrc.init.in
files/fail2ban.service.in
files/fail2ban-tmpfiles.conf
files/fail2ban.socket.in
files/fail2ban.upstart
files/gen_badbots
files/ipmasq-ZZZzzz_fail2ban.rul

View file

@ -2,7 +2,7 @@
/ _|__ _(_) |_ ) |__ __ _ _ _
| _/ _` | | |/ /| '_ \/ _` | ' \
|_| \__,_|_|_/___|_.__/\__,_|_||_|
v1.1.0.dev1 20??/??/??
v1.1.2.dev1 20??/??/??
## Fail2Ban: ban hosts that cause multiple authentication errors

View file

@ -35,7 +35,11 @@ type = multiport
rule_match-custom =
rule_match-allports = meta l4proto \{ <protocol> \}
rule_match-multiport = $proto dport \{ $(echo '<port>' | sed s/:/-/g) \}
rule_match-allprotos-custom =
rule_match-allprotos-allports =
rule_match-allprotos-multiport = th dport \{ $(echo '<port>' | sed s/:/-/g) \}
match = <rule_match-<type>>
match_allprotos = <rule_match-allprotos-<type>>
# Option: rule_stat
# Notes.: statement for nftables filter rule.
@ -43,6 +47,7 @@ match = <rule_match-<type>>
# Values: nftables statement
#
rule_stat = %(match)s <addr_family> saddr @<addr_set> <blocktype>
rule_stat_allprotos = %(match_allprotos)s <addr_family> saddr @<addr_set> <blocktype>
# optional iterator over protocol's:
_nft_for_proto-custom-iter =
@ -56,9 +61,13 @@ _nft_list = <nftables> -a list chain <table_family> <table> <chain>
_nft_get_handle_id = sed -nE 's/.*@<addr_set>\s+.*\s+\#\s*(handle\s+[0-9]+)$/\1/p'
_nft_add_set = <nftables> add set <table_family> <table> <addr_set> \{ type <addr_type>\;<addr_options> \}
if [ '<protocol>' != "all" ]; then
<_nft_for_proto-<type>-iter>
<nftables> add rule <table_family> <table> <chain> %(rule_stat)s
<_nft_for_proto-<type>-done>
else
<nftables> add rule <table_family> <table> <chain> %(rule_stat_allprotos)s
fi
_nft_del_set = { %(_nft_list)s | %(_nft_get_handle_id)s; } | while read -r hdl; do
<nftables> delete rule <table_family> <table> <chain> $hdl; done
<nftables> delete set <table_family> <table> <addr_set>

View file

@ -17,12 +17,12 @@ before = apache-common.conf
[Definition]
script = /\S*(?:php(?:[45]|[.-]cgi)?|\.asp|\.exe|\.pl|\bcgi-bin/)
script = /\S*(?:php(?:[45]|[.-]cgi)?|\.asp|\.exe|\.pl|\bcgi-bin)
prefregex = ^%(_apache_error_client)s (?:AH0(?:01(?:28|30)|1(?:264|071)|2811): )?(?=(?:[Ff]ile|[Ss]cript|[Gg]ot error|stderr from) )<F-CONTENT>.+</F-CONTENT>$
failregex = ^(?:(?:[Ff]ile does not exist|[Ss]cript not found or unable to stat): <script>\b|[Gg]ot error '[Pp]rimary script unknown\b)
^(?:stderr from |script (?P<_q>'))<script>\S*(?(_q)'|) (?:script )?(?:does not exist|not found or unable to stat)
^(?:stderr from |script (?P<_q>'))<script>\b\S*(?(_q)'|) (?:script )?(?:does not exist|not found or unable to stat)
ignoreregex =

View file

@ -82,8 +82,7 @@ datepattern = %(lt_short/datepattern)s
[lt_rfc5424]
# RFC 5424 log-format, see gh-2309:
#__prefix_line = \s*<__hostname> <__daemon_re> \d+ \S+ \S+\s+
__prefix_line = \s*<__hostname> <__daemon_re> \d+ \S+ (?:[^\[\]\s]+|(?:\[(?:[^\]"]*|"[^"]*")*\])+)\s+
__prefix_line = \s*<__hostname> <__daemon_re> \d+ \S+ (?:-|(?:\[(?:[^\]"]|"(?:[^"\\]|\\.)*")*\])+)\s+
datepattern = ^<\d+>\d+\s+{DATE}
# Author: Yaroslav Halchenko, Sergey G. Brester (aka sebres)

View file

@ -0,0 +1,22 @@
# Fail2Ban filter for Cowrie SSH/Telnet honeypot (https://github.com/cowrie/cowrie)
# Detects new connections logged in Cowrie's JSON output format
# (log.format = json / output.jsonlog in cowrie.cfg), one JSON object per line, e.g.:
# {"session":"2a5e755afa79","protocol":"ssh","src_ip":"1.2.3.4","src_port":41000,
# "dst_ip":"172.18.0.2","dst_port":2222,"eventid":"cowrie.session.connect",
# "sensor":"myhost","uuid":"8c5b1af2-...","timestamp":"2024-01-01T00:00:00.000000Z",
# "message":"New connection: ..."}
# As Cowrie is a honeypot, any connection to it is unsolicited by definition.
[Init]
datepattern = "timestamp":"(%%ExY-%%m-%%dT%%H:%%M:%%S(?:\.%%f)?Z?)"
[Definition]
# bounded run of arbitrary "key":value pairs (string/number), used to skip
# over fields we don't care about without resorting to an unbounded ".*"
_kv = (?:\s*"(?!(?:src_ip|eventid)\b)\w[^"]+"\s*:\s*(?:"[^"]+"|\w+)\s*[,\}]){0,20}
datepattern = ^\{%(_kv)s("timestamp":"%%Y-%%m-%%dT%%H:%%M:%%S\.%%f%%z"\s*,\s*)
failregex = ^\{%(_kv)s"src_ip":"<ADDR>",%(_kv)s"eventid":"cowrie\.session\.connect"
ignoreregex =

View file

@ -23,10 +23,7 @@
[Definition]
# Option: failregex
# Notes.: regex to match the password failure messages in the logfile. The
# host must be matched by a group named "host". The tag "<HOST>" can
# be used for standard IP/hostname matching and is only an alias for
# (?:::f{4,6}:)?(?P<host>\S+)
# Notes.: regex to match the password failure messages in the logfile.
# Values: TEXT
#
# Sample log entries (used different time formats and an extra sample with process info in front of date)
@ -37,9 +34,9 @@
__prefix = (?:\[[^\]]+\])?\s*
__opt_data = (?::|\s+\[[^\]]+\])
failregex = ^%(__prefix)sSMTP Server%(__opt_data)s Authentication failed for user .*? \; connecting host \[?<HOST>\]?$
^%(__prefix)ssmtp: (?:[^\[]+ )*\[?<HOST>\]? authentication failure using internet password\s*$
^%(__prefix)sSMTP Server%(__opt_data)s Connection from \[?<HOST>\]? rejected for policy reasons\.
failregex = ^%(__prefix)sSMTP Server%(__opt_data)s Authentication failed for user .*? \; connecting host \[?<ADDR>\]?$
^%(__prefix)ssmtp: (?:<F-USER>(?:[^\[ ]+ )*[^\[ ]+</F-USER> )?\[?<ADDR>\]? authentication failure using internet password\s*$
^%(__prefix)sSMTP Server%(__opt_data)s Connection from \[?<ADDR>\]? rejected for policy reasons\.
# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.

View file

@ -15,7 +15,7 @@ _auth_driver = [a-z\-]+
_auth_worker = (?:dovecot: )?auth(?:-worker)?
_auth_worker_info = (?:%(_auth_driver)s: )?(?:conn \w+:auth(?:-worker)? \([^\)]+\): auth(?:-worker)?<\d+>: )?
_bypass_reject_reason = (?:: (?:\w+\([^\):]*\) \w+|[^\(]+))*
_bypass_reject_reason = (?::(?: [^\( ]*|\([^\(]*\))*)?
prefregex = ^%(__prefix_line)s(?:%(_auth_worker)s(?:\((?:\S*,<ADDR>(?:,\S*)?|[^\)]+)\))?(?:<[^>]+>)?: )?(?:%(__pam_auth)s(?:\(dovecot:auth\))?: |(?:pop3|imap|managesieve|submission)-login: )?(?:request \[\d+\]: )?(?:Info: )?%(_auth_worker_info)s<F-CONTENT>.+</F-CONTENT>$

View file

@ -1,6 +1,32 @@
# Fail2Ban filter for Gitlab
# Detecting unauthorized access to the Gitlab Web portal
# typically logged in /var/log/gitlab/gitlab-rails/application.log
#
# logtype "file" (default) reads the legacy application.log, typically logged in
# /var/log/gitlab/gitlab-rails/application.log
#
# logtype "json" reads the structured application_json.log that superseded it
# (application.log was deprecated by Gitlab in 15.10), typically logged in
# /var/log/gitlab/gitlab-rails/application_json.log
[Definition]
# Log format: "file" (application.log) or "json" (application_json.log)
logtype = file
failregex = <lt_<logtype>/failregex>
datepattern = <lt_<logtype>/datepattern>
ignoreregex =
[lt_file]
failregex = ^: Failed Login: username=<F-USER>.+</F-USER> ip=<HOST>$
datepattern = {^LN-BEG}%%ExY(?P<_sep>[-/.])%%m(?P=_sep)%%d[T ]%%H:%%M:%%S(?:[.,]%%f)?(?:\s*%%z)?
[lt_json]
# bounded run of "key":value pairs, used to skip over fields we don't care
# about (order of tags in the message is significant, so relative positions
# of time, meta.remote_ip and message are anchored below)
_groupre = (?:\s*"(?!(?:time|meta\.remote_ip|message)\b)\w[^"]+"\s*:\s*(?:"[^"]+"|\w+)\s*[,\}])
failregex = ^%(_groupre)s*"meta\.remote_ip":"<ADDR>"\s*,\s*%(_groupre)s*"message":"Failed Login: username=<F-USER>(?:\S*|[^"]*?)(?= ip=)</F-USER>
datepattern = ^\{%(_groupre)s*"time":"%%Y-%%m-%%dT%%H:%%M:%%S\.%%f%%z"\s*,\s*

View file

@ -5,11 +5,12 @@
# Load regexes for filtering
before = botsearch-common.conf
nginx-error-common.conf
[Definition]
failregex = ^<HOST> \- \S+ \[\] \"(GET|POST|HEAD) \/<block> \S+\" 404 .+$
^ \[error\] \d+#\d+: \*\d+ (\S+ )?\"\S+\" (failed|is not found) \(2\: No such file or directory\), client\: <HOST>\, server\: \S*\, request: \"(GET|POST|HEAD) \/<block> \S+\"\, .*?$
^%(__prefix_line)s(\S+ )?\"\S+\" (failed|is not found) \(2\: No such file or directory\), client\: <HOST>\, server\: \S*\, request: \"(GET|POST|HEAD) \/<block> \S+\"\, .*?$
ignoreregex =

View file

@ -59,6 +59,7 @@ mdad-extra =
mdpr-aggressive = (?:%(mdpr-auth)s|%(mdpr-normal)s|%(mdpr-ddos)s)
mdre-aggressive = %(mdre-auth2)s
%(mdre-normal)s
^hostname \S+ does not resolve to address <ADDR>
mdad-aggressive = %(mdad-ddos)s
mdpr-errors = too many errors after \S+

View file

@ -865,6 +865,7 @@ protocol = udp
logpath = /usr/local/vpnserver/security_log/*/sec.log
[gitlab]
# for the newer application_json.log set: filter = gitlab[mode=json]
port = http,https
logpath = /var/log/gitlab/gitlab-rails/application.log
@ -995,3 +996,7 @@ logpath = /var/log/vaultwarden.log
[xrdp]
port = 3389
logpath = /var/log/xrdp-sesman.log
[cowrie]
port = ssh,telnet,2222,2223
logpath = /var/log/cowrie/cowrie.json

View file

@ -9,6 +9,9 @@ after = paths-overrides.local
[DEFAULT]
banaction = nftables
banaction_allports = nftables[type=allports]
apache_error_log = /var/log/httpd/*error_log
apache_access_log = /var/log/httpd/*access_log

View file

@ -9,6 +9,9 @@ after = paths-overrides.local
[DEFAULT]
banaction = nftables
banaction_allports = nftables[type=allports]
syslog_mail = /var/log/maillog
syslog_mail_warn = /var/log/maillog

View file

@ -9,6 +9,9 @@ after = paths-overrides.local
[DEFAULT]
banaction = pf
banaction_allports = pf[actiontype=<allports>]
# http://www.freebsd.org/doc/handbook/configtuning-syslog.html
#
syslog_mail = /var/log/maillog

View file

@ -9,12 +9,27 @@ after = paths-overrides.local
[DEFAULT]
banaction = nftables
banaction_allports = nftables[type=allports]
syslog_local0 = /var/log/messages
syslog_mail = /var/log/mail
syslog_mail_warn = %(syslog_mail)s
syslog_authpriv = %(syslog_local0)s
syslog_user = %(syslog_local0)s
syslog_ftp = %(syslog_local0)s
syslog_daemon = %(syslog_local0)s
apache_error_log = /var/log/apache2/*error_log
apache_access_log = /var/log/apache2/*access_log
pureftpd_log = %(syslog_local0)s
exim_main_log = /var/log/exim/main.log

View file

@ -10,6 +10,9 @@ after = paths-overrides.local
[DEFAULT]
banaction = pf
banaction_allports = pf[actiontype=<allports>]
syslog_mail = /var/log/mail.log
syslog_mail_warn = /var/log/mail.warn

View file

@ -201,6 +201,7 @@ class AsyncServer(asyncore.dispatcher):
asyncore.dispatcher.__init__(self)
self.__transmitter = transmitter
self.__sock = "/var/run/fail2ban/fail2ban.sock"
self.__shutdown_socket = True
self.__init = False
self.__active = False
self.__errCount = {'accept': 0, 'listen': 0}
@ -236,35 +237,56 @@ class AsyncServer(asyncore.dispatcher):
# Creates an instance of the handler class to handle the
# request/response on the incoming connection.
RequestHandler(conn, self.__transmitter)
def setup_socket(self, sock, force):
nfds = os.environ.get("LISTEN_FDS")
if nfds and os.environ.get("LISTEN_PID") == str(os.getpid()):
# systemd socket activation (sd_listen_fds(3)): reuse an inherited,
# already bound and listening socket instead of creating our own.
names = os.environ.get("LISTEN_FDNAMES", "").split(":")
idx = names.index("fail2ban") if "fail2ban" in names else 0
idx = min(idx, int(nfds) - 1)
os.environ.pop("LISTEN_PID", None)
os.environ.pop("LISTEN_FDS", None)
os.environ.pop("LISTEN_FDNAMES", None)
logSys.info("Using socket activation, inherited socket %r", sock)
# prevent socket from being shut down on close
self.__shutdown_socket = False
self.set_socket(socket.socket(socket.AF_UNIX, socket.SOCK_STREAM, fileno=3 + idx))
self.socket.setblocking(False)
self.accepting = True
else:
# Remove socket
if os.path.exists(sock):
logSys.error("Fail2ban seems to be already running")
if force:
logSys.warning("Forcing execution of the server")
self._remove_sock()
else:
raise AsyncServerException("Server already running")
# Creates the socket.
self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.set_reuse_addr()
try:
self.bind(sock)
except Exception: # pragma: no cover
raise AsyncServerException("Unable to bind socket %s" % self.__sock)
self.listen(1)
# Sets the init flag.
self.__init = True
AsyncServer.__markCloseOnExec(self.socket)
##
# Starts the communication server.
#
# @param sock: socket file.
# @param force: remove the socket file if exists.
def start(self, sock, force, timeout=None, use_poll=False):
self.__worker = threading.current_thread()
self.__sock = sock
# Remove socket
if os.path.exists(sock):
logSys.error("Fail2ban seems to be already running")
if force:
logSys.warning("Forcing execution of the server")
self._remove_sock()
else:
raise AsyncServerException("Server already running")
# Creates the socket.
self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.set_reuse_addr()
try:
self.bind(sock)
except Exception: # pragma: no cover
raise AsyncServerException("Unable to bind socket %s" % self.__sock)
AsyncServer.__markCloseOnExec(self.socket)
self.listen(1)
# Sets the init flag.
self.__init = self.__loop = self.__active = True
self.setup_socket(sock, force)
self.__loop = self.__active = True
# Execute on start event (server ready):
if self.onstart:
self.onstart()
@ -279,7 +301,7 @@ class AsyncServer(asyncore.dispatcher):
if self.__active:
self.__loop = False
# shutdown socket here:
if self.socket:
if self.__shutdown_socket and self.socket:
try:
self.socket.shutdown(socket.SHUT_RDWR)
except socket.error: # pragma: no cover - normally unreachable

View file

@ -23,6 +23,7 @@ __copyright__ = "Copyright (c) 2013 Steven Hiscocks"
__license__ = "GPL"
import os
import threading
import time
from glob import glob
@ -60,11 +61,11 @@ def _globJournalFiles(flags=None, path=None):
"""add journal files to set corresponding path and flags (without rotated *@*.journal)"""
# system journal:
if (flags is None) or (flags & journal.SYSTEM_ONLY):
filesSet |= set(glob(_join(p,'system.journal'))) - set(glob(_join(p,'system*@*.journal')))
filesSet |= set(glob(_join(p,'system.journal')))
# current user-journal:
if (flags is not None) and (flags & journal.CURRENT_USER):
uid = os.geteuid()
filesSet |= set(glob(_join(p,('user-%s.journal' % uid)))) - set(glob(_join(p,('user-%s@*.journal' % uid))))
filesSet |= set(glob(_join(p,('user-%s.journal' % uid))))
# all local journals:
if (flags is None) or not (flags & (journal.SYSTEM_ONLY|journal.CURRENT_USER)):
filesSet |= set(glob(_join(p,'*.journal'))) - set(glob(_join(p,'*@*.journal')))
@ -102,6 +103,7 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
JournalFilter.__init__(self, jail, **kwargs)
self.__modified = 0
# Initialise systemd-journal connection
self.__lock = threading.Lock()
self.__journal = journal.Reader(**self.__jrnlargs)
self.__matches = []
self.__bypassInvalidateMsg = 0
@ -170,7 +172,7 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
"""
try:
# open?
if self.__journal.closed: # pragma: no cover
if not self.__journal or self.__journal.closed: # pragma: no cover
return False
# has cursor? if it is broken (e. g. no descriptor) - it'd raise this:
# OSError: [Errno 99] Cannot assign requested address
@ -196,10 +198,10 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
ja.get('flags', 0), ja.get('path'), ja.get('files'), ja.get('namespace'))
except:
# cannot reopen in that way, so simply recreate reader:
self.closeJournal()
self._closeJournal()
self.__journal = journal.Reader(**self.__jrnlargs)
# restore journalmatch specified for the jail:
self.resetJournalMatches()
self._resetJournalMatches()
# just to avoid "Invalidate signaled" happening again after reopen:
self.__bypassInvalidateMsg = MyTime.time() + 1
@ -226,28 +228,32 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
# @param match journalctl syntax matches in list structure
def addJournalMatch(self, match):
newMatches = [[]]
for match_element in match:
if match_element == "+":
newMatches.append([])
with self.__lock:
if not self.__journal or self.__journal.closed:
return
newMatches = [[]]
for match_element in match:
if match_element == "+":
newMatches.append([])
else:
newMatches[-1].append(match_element)
try:
self._addJournalMatches(newMatches)
except ValueError:
logSys.error(
"Error adding journal match for: %r", " ".join(match))
self._resetJournalMatches()
raise
else:
newMatches[-1].append(match_element)
try:
self._addJournalMatches(newMatches)
except ValueError:
logSys.error(
"Error adding journal match for: %r", " ".join(match))
self.resetJournalMatches()
raise
else:
logSys.info("[%s] Added journal match for: %r", self.jailName,
" ".join(match))
logSys.info("[%s] Added journal match for: %r", self.jailName,
" ".join(match))
##
# Reset a journal match filter called on removal or failure
#
# @return None
def resetJournalMatches(self):
def _resetJournalMatches(self):
self.__journal.flush_matches()
logSys.debug("[%s] Flushed all journal matches", self.jailName)
match_copy = self.__matches[:]
@ -266,19 +272,22 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
# @param match journalctl syntax matches
def delJournalMatch(self, match=None):
# clear all:
if match is None:
if not self.__matches:
with self.__lock:
if not self.__journal or self.__journal.closed:
return
del self.__matches[:]
# delete by index:
elif match in self.__matches:
del self.__matches[self.__matches.index(match)]
else:
raise ValueError("Match %r not found" % match)
self.resetJournalMatches()
logSys.info("[%s] Removed journal match for: %r", self.jailName,
match if match else '*')
# clear all:
if match is None:
if not self.__matches:
return
del self.__matches[:]
# delete by index:
elif match in self.__matches:
del self.__matches[self.__matches.index(match)]
else:
raise ValueError("Match %r not found" % match)
self._resetJournalMatches()
logSys.info("[%s] Removed journal match for: %r", self.jailName,
match if match else '*')
##
# Get current journal match filter
@ -457,10 +466,11 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
except OSError:
pass
# if it is not alive - reopen:
if not self._journalAlive:
logSys.log(logging.MSG, "[%s] Journal reader seems to be offline, reopen journal", self.jailName)
self._reopenJournal()
wcode = journal.NOP
with self.__lock:
if not self._journalAlive:
logSys.log(logging.MSG, "[%s] Journal reader seems to be offline, reopen journal", self.jailName)
self._reopenJournal()
wcode = journal.NOP
self.__modified = 0
while self.active:
logentry = None
@ -527,7 +537,7 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
logSys.debug("[%s] filter exited (systemd)", self.jailName)
return True
def closeJournal(self):
def _closeJournal(self):
try:
jnl, self.__journal = self.__journal, None
if jnl:
@ -558,8 +568,9 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
def afterStop(self):
"""Cleanup"""
# close journal:
self.closeJournal()
with self.__lock:
# close journal:
self._closeJournal()
# ensure positions of pending logs are up-to-date:
if self._pendDBUpdates and self.jail.database:
self._updateDBPending()

View file

@ -26,3 +26,6 @@
# failJSON: { "time": "2024-12-18T23:58:03", "match": true , "host": "192.0.2.74", "desc": "script not found, changed log-format with stderr from (gh-3900)" }
[Wed Dec 18 23:58:03.148113 2024] [cgi:error] [pid 16720:tid 1878] [client 192.0.2.74:35092] AH02811: stderr from /usr/lib/cgi-bin/luci: script not found or unable to stat
# failJSON: { "time": "2025-07-30T16:43:26", "match": true , "host": "192.0.2.5", "desc": "stderr from a cgi-bin directory, no script name and no trailing slash (gh-4040)" }
[Wed Jul 30 16:43:26.968908 2025] [cgi:error] [pid 147397:tid 147442] [client 192.0.2.5:33570] AH02811: stderr from /srv/http/cgi-bin: script not found or unable to stat

View file

@ -0,0 +1,28 @@
# Cowrie honeypot JSON log (log.format = json), real-world samples
# failJSON: { "time": "2026-08-13T00:00:34", "match": true, "host": "137.184.154.9" }
{"session":"2a5e755afa79","protocol":"ssh","src_ip":"137.184.154.9","src_port":37416,"dst_ip":"172.18.0.2","dst_port":2222,"eventid":"cowrie.session.connect","sensor":"580d0186ebeb","uuid":"8c5b1af2-8fc4-11f1-9350-ee70fe9b2de3","timestamp":"2026-08-13T00:00:34.803989Z","message":"New connection: 137.184.154.9:37416 (172.18.0.2:2222) [session: 2a5e755afa79]"}
# not a connect event (client version) -> should not match
# failJSON: { "match": false }
{"session":"2a5e755afa79","protocol":"ssh","src_ip":"137.184.154.9","src_port":37416,"dst_ip":"172.18.0.2","dst_port":2222,"version":"SSH-2.0-Go","eventid":"cowrie.client.version","sensor":"580d0186ebeb","uuid":"8c5b1af2-8fc4-11f1-9350-ee70fe9b2de3","timestamp":"2026-08-13T00:00:34.808236Z","message":"Remote SSH version: SSH-2.0-Go"}
# not a connect event (kex, has arrays/nested values) -> should not match
# failJSON: { "match": false }
{"session":"2a5e755afa79","protocol":"ssh","src_ip":"137.184.154.9","src_port":37416,"dst_ip":"172.18.0.2","dst_port":2222,"hassh":"16443846184eafde36765c9bab2f4397","hasshAlgorithms":"mlkem768x25519-sha256,curve25519-sha256","kexAlgs":["mlkem768x25519-sha256","curve25519-sha256"],"keyAlgs":["rsa-sha2-256-cert-v01@openssh.com","ssh-ed25519"],"encCS":["aes128-gcm@openssh.com","aes256-gcm@openssh.com"],"macCS":["hmac-sha2-256-etm@openssh.com"],"compCS":["none"],"langCS":[""],"eventid":"cowrie.client.kex","sensor":"580d0186ebeb","uuid":"8c5b1af2-8fc4-11f1-9350-ee70fe9b2de3","timestamp":"2026-08-13T00:00:34.924330Z","message":"SSH client hassh fingerprint: 16443846184eafde36765c9bab2f4397"}
# not a connect event (login success) -> should not match
# failJSON: { "match": false }
{"session":"2a5e755afa79","protocol":"ssh","src_ip":"137.184.154.9","src_port":37416,"dst_ip":"172.18.0.2","dst_port":2222,"username":"brun","password":"brun","eventid":"cowrie.login.success","sensor":"580d0186ebeb","uuid":"8c5b1af2-8fc4-11f1-9350-ee70fe9b2de3","timestamp":"2026-08-13T00:00:35.273750Z","message":"login attempt [brun/brun] succeeded"}
# not a connect event (log closed) -> should not match
# failJSON: { "match": false }
{"session":"2a5e755afa79","protocol":"ssh","src_ip":"137.184.154.9","src_port":37416,"dst_ip":"172.18.0.2","dst_port":2222,"ttylog":"var/lib/cowrie/tty/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","size":30,"shasum":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","duplicate":true,"duration_ms":5,"eventid":"cowrie.log.closed","sensor":"580d0186ebeb","uuid":"8c5b1af2-8fc4-11f1-9350-ee70fe9b2de3","timestamp":"2026-08-13T00:00:35.513067Z","message":"Closing TTY Log: var/lib/cowrie/tty/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 after 5 milliseconds"}
# a second, different connect event -> should match
# failJSON: { "time": "2026-08-13T00:05:00", "match": true, "host": "203.0.113.9" }
{"session":"9f8e7d6c5b4a","protocol":"telnet","src_ip":"203.0.113.9","src_port":52344,"dst_ip":"172.18.0.2","dst_port":23,"eventid":"cowrie.session.connect","sensor":"580d0186ebeb","uuid":"9f8e7d6c-8fc4-11f1-9350-ee70fe9b2de3","timestamp":"2026-08-13T00:05:00.000000Z","message":"New connection: 203.0.113.9:52344 (172.18.0.2:23) [session: 9f8e7d6c5b4a]"}
# malformed / non-JSON line -> should not match
# failJSON: { "match": false }
not a json line at all

View file

@ -7,6 +7,14 @@
# failJSON: { "time": "2016-11-07T22:21:20", "match": true , "host": "1.2.3.4" }
2016-11-07 22:21:20 smtp: postmaster [1.2.3.4] authentication failure using internet password
# failJSON: { "time": "2016-11-07T22:21:21", "match": true , "host": "192.0.2.4", "user": "user with space in name" }
2016-11-07 22:21:21 smtp: user with space in name [192.0.2.4] authentication failure using internet password
# failJSON: { "time": "2016-11-07T22:21:22", "match": true , "host": "192.0.2.5", "desc": "GHSA-33wh-ccjc-p397, positive case (match, no hangs)" }
2016-11-07 22:21:22 smtp: A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A 192.0.2.5 authentication failure using internet password
# failJSON: { "match": false, "desc": "GHSA-33wh-ccjc-p397, negative case (bypass, no hangs)" }
2016-11-07 22:21:23 smtp: A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A 192.0.2.5
# failJSON: { "time": "2018-09-19T17:25:50", "match": true , "host": "192.0.2.1", "desc":"different log-format" }
2018-09-19 17:25:50 SMTP Server [0D14:0027-1334] Authentication failed for user Bad Hacker ; connecting host [192.0.2.1]
# failJSON: { "time": "2018-09-19T17:25:52", "match": true , "host": "192.0.2.2", "desc":"gh-2228, rejected for policy reasons" }

View file

@ -144,6 +144,11 @@ Aug 30 00:10:57 auth(info@example.com,192.0.2.110,sasl:login)<CvN+E95Uf4e7eEnj>:
# failJSON: { "time": "2004-08-30T00:11:05", "match": true , "host": "192.0.2.110", "desc": "closed after 3 attempts (password mismatch), gh-4196" }
Aug 30 00:11:05 imap-login: Info: Login aborted: Connection closed (auth failed, 3 attempts in 16 secs) (auth_failed): user=<info@example.com>, method=PLAIN, rip=192.0.2.110, lip=0.0.0.0, TLS, session=<CvN+E95Uf4e7eEnj>
# failJSON: { "time": "2004-08-31T16:06:05", "match": true , "host": "192.0.2.111", "desc": "GHSA-33wh-ccjc-p397, positive case (match, no hangs)" }
Aug 31 16:06:05 s166-62-100-187 dovecot: imap-login: Disconnected: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: (reason: some unknown reason) (tried to use disallowed test auth): user=<>, rip=192.0.2.111
# failJSON: { "match": false , "desc": "GHSA-33wh-ccjc-p397, negative case (bypass, no hangs)" }
Aug 31 16:06:06 s166-62-100-187 dovecot: imap-login: Disconnected: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: test no match
# ---------------------------------------
# Test-cases of aggressive mode:
# ---------------------------------------

View file

@ -3,3 +3,14 @@
2020-04-09T14:04:00.667Z: Failed Login: username=admin ip=80.10.11.12
# failJSON: { "time": "2020-04-09T16:15:09", "match": true , "host": "80.10.11.12" }
2020-04-09T14:15:09.344Z: Failed Login: username=user name ip=80.10.11.12
# Access of unauthorized host in /var/log/gitlab/gitlab-rails/application_json.log
# filterOptions: [{"logtype": "json"}]
# failJSON: { "time": "2023-08-25T13:25:19", "match": true , "host": "87.250.224.11" }
{"severity":"INFO","time":"2023-08-25T11:25:19.979Z","correlation_id":"01H8P721YTW1YH5Q7YCSJPVJ5T","meta.caller_id":"SessionsController#new","meta.remote_ip":"87.250.224.11","meta.feature_category":"system_access","meta.client_id":"ip/87.250.224.11","message":"Failed Login: username=admin ip=87.250.224.11"}
# failJSON: { "time": "2023-08-25T13:31:07", "match": true , "host": "2001:db8::1" }
{"severity":"INFO","time":"2023-08-25T11:31:07.512Z","correlation_id":"01H8P73ABCDEF1234567890ABCD","meta.caller_id":"SessionsController#new","meta.remote_ip":"2001:db8::1","meta.feature_category":"system_access","meta.client_id":"ip/2001:db8::1","message":"Failed Login: username=git ip=2001:db8::1"}
# a successful login must not match
# failJSON: { "match": false }
{"severity":"INFO","time":"2023-08-25T11:55:10.525Z","correlation_id":"01H8P8RPMVW53W0KWEN2H2DNF4","meta.caller_id":"SessionsController#create","meta.remote_ip":"203.0.113.9","meta.feature_category":"system_access","meta.user":"admin","meta.user_id":2,"meta.client_id":"user/2","message":"Successful Login: username=admin ip=203.0.113.9 method=standard admin=true"}

View file

@ -32,4 +32,12 @@
2015/01/21 10:58:10 [error] 2833#0: *16336 open() "/var/www/site/cgi-bin/php4" failed (2: No such file or directory), client: 5.7.9.2, server: localhost, request: "HEAD /cgi-bin/php4 HTTP/1.1", host: "1.2.3.4"
# failJSON: { "time": "2015-01-21T15:02:27", "match": true , "host": "5.7.9.2" }
2015/01/21 15:02:27 [error] 2833#0: *16813 "/var/www/site/roundcube/" is not found (2: No such file or directory), client: 5.7.9.2, server: localhost, request: "GET /roundcube/ HTTP/1.1", host: "1.2.3.4"
2015/01/21 15:02:27 [error] 2833#0: *16813 "/var/www/site/roundcube/" is not found (2: No such file or directory), client: 5.7.9.2, server: localhost, request: "GET /roundcube/ HTTP/1.1", host: "1.2.3.4"
# filterOptions: [{"logtype": "journal"}]
# failJSON: { "match": true, "host": "5.7.9.2", "desc": "systemd journal message, gh-3732" }
host nginx[2833]: 2015/01/21 10:56:10 [error] 2833#0: *16336 open() "/var/www/site/cgi-bin/php4" failed (2: No such file or directory), client: 5.7.9.2, server: localhost, request: "GET /cgi-bin/php4 HTTP/1.1", host: "1.2.3.4"
# failJSON: { "match": true, "host": "5.7.9.2", "desc": "systemd journal message, is not found variant, gh-3732" }
host nginx[2833]: 2015/01/21 15:02:27 [error] 2833#0: *16813 "/var/www/site/roundcube/" is not found (2: No such file or directory), client: 5.7.9.2, server: localhost, request: "GET /roundcube/ HTTP/1.1", host: "1.2.3.4"

View file

@ -154,6 +154,9 @@ Jan 14 16:18:16 xxx postfix/smtpd[14933]: warning: host[192.0.2.5]: SASL CRAM-MD
# 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
# failJSON: { "match": true , "host": "192.0.2.44", "desc": "hostname does not resolve to address, aggressive only (gh-4078)" }
Aug 14 10:00:01 mail postfix/smtpd[1234]: warning: hostname bad.example.com does not resolve to address 192.0.2.44: Name or service not known
# ---------------------------------------
# Test-cases of postfix DDOS mode:
# ---------------------------------------

View file

@ -414,4 +414,11 @@ Nov 26 16:47:51 srv sshd[19320]: Disconnected from authenticating user root 192.
# failJSON: { "time": "2019-07-08T23:40:17", "match": true , "host": "192.0.2.2", "desc": "RFC 5424 format with extra data and brackets in value (gh-2309)" }
<38>1 2019-07-08T17:40:17.954167-04:00 hostname.example.com sshd 55555 - [timeQuality tzKnown="1" isSynced="0"][xxx@123 test="[brackets]"] Failed unknown for invalid user redmond from 192.0.2.2 port 55555 ssh2
# failJSON: { "match": false , "desc": "RFC 5424 format, injection attempt on STRUCTURED-DATA field (GHSA-c2mg-2r7g-c3wp)" }
<38>1 2019-07-08T17:40:17.954169-04:00 hostname.example.com sshd 12345 123 [AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
# failJSON: { "match": false , "desc": "RFC 5424 format, injection attempt on STRUCTURED-DATA 2nd field (GHSA-c2mg-2r7g-c3wp)" }
<38>1 2019-07-08T17:40:17.954170-04:00 hostname.example.com sshd 12345 123 [AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA][BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
# failJSON: { "match": false , "desc": "RFC 5424 format, injection attempt on STRUCTURED-DATA, unescaped new-line in PARAM-VALUE (GHSA-c2mg-2r7g-c3wp)" }
<38>1 2019-07-08T17:40:17.954171-04:00 hostname.example.com sshd 12345 123 [AAAAAAAAAAAAAAAAAAAAAAAAAA XXX="CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
# addFILE: "sshd-journal"

View file

@ -25,6 +25,7 @@ __copyright__ = "Copyright (c) 2013 Steven Hiscocks"
__license__ = "GPL"
import os
import socket
import sys
import tempfile
import threading
@ -34,6 +35,7 @@ import unittest
from .utils import LogCaptureTestCase
from .. import protocol
from ..server import asyncserver
from ..server.asyncserver import asyncore, RequestHandler, loop, AsyncServer, AsyncServerException
from ..server.utils import Utils
from ..client.csocket import CSocket
@ -203,6 +205,44 @@ class Socket(LogCaptureTestCase):
self.assertLogged("Server connection was closed: test errors in poll",
"Too many errors - stop logging connection errors", all=True)
def testSocketActivation(self):
# Simulate a socket already bound/listening, handed to us as an
# inherited fd (systemd socket activation, sd_listen_fds(3)):
presock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
presock.bind(self.sock_name)
presock.listen(5)
# dup to a fresh fd first (presock.fileno() might already be 3):
fd = os.dup(presock.fileno())
# keep backup fd to allow for shutdown test
self.addCleanup(os.close, fd)
presock.close()
os.dup2(fd, 3)
self.addCleanup(os.environ.pop, "LISTEN_PID", None)
self.addCleanup(os.environ.pop, "LISTEN_FDS", None)
self.addCleanup(lambda: os.path.exists(self.sock_name) and os.remove(self.sock_name))
os.environ["LISTEN_PID"] = str(os.getpid())
os.environ["LISTEN_FDS"] = "1"
serverThread = self._createServerThread()
# consumed once picked up, so forked actions don't misinterpret them:
self.assertNotIn("LISTEN_PID", os.environ)
self.assertNotIn("LISTEN_FDS", os.environ)
client = Utils.wait_for(self._serverSocket, 2)
testMessage = ["A", "test", "message"]
self.assertEqual(client.send(testMessage), testMessage)
self.server.stop()
self._stopServerThread()
self.assertFalse(serverThread.is_alive())
# socket file is owned by the service manager, must not be removed:
self.assertTrue(os.path.exists(self.sock_name))
# socket is owned by the service manager, must not be shut down:
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as csock:
csock.connect(self.sock_name)
def testSocketForce(self):
open(self.sock_name, 'w').close() # Create sock file
# Try to start without force

View file

@ -24,7 +24,7 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko, Steven Hiscocks, Daniel Black"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2005-2016 Yaroslav Halchenko, 2013-2014 Steven Hiscocks, Daniel Black"
__license__ = "GPL-v2+"
version = "1.1.1.dev1"
version = "1.1.2.dev1"
def normVersion():
""" Returns fail2ban version in normalized machine-readable format"""

View file

@ -8,6 +8,8 @@ PartOf=iptables.service firewalld.service ip6tables.service ipset.service nftabl
Type=simple
Environment="PYTHONNOUSERSITE=1"
RuntimeDirectory=fail2ban
# prevents socket created by fail2ban.socket from being removed
RuntimeDirectoryPreserve=yes
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

12
files/fail2ban.socket.in Normal file
View file

@ -0,0 +1,12 @@
[Unit]
Description=Fail2Ban Socket
Documentation=man:fail2ban(1)
[Socket]
ListenStream=%t/fail2ban/fail2ban.sock
FileDescriptorName=fail2ban
SocketMode=0600
RuntimeDirectory=fail2ban
[Install]
WantedBy=sockets.target

View file

@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH FAIL2BAN-CLIENT "1" "April 2024" "Fail2Ban v1.1.1.dev1" "User Commands"
.TH FAIL2BAN-CLIENT "1" "August 2026" "Fail2Ban v1.1.2.dev1" "User Commands"
.SH NAME
fail2ban-client \- configure and control the server
.SH SYNOPSIS
.B fail2ban-client
[\fI\,OPTIONS\/\fR] \fI\,<COMMAND>\/\fR
.SH DESCRIPTION
Fail2Ban v1.1.1.dev1 reads log file that contains password failure report
Fail2Ban v1.1.2.dev1 reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules.
.SH OPTIONS
.TP
@ -125,8 +125,10 @@ server
.TP
\fBstatus \fB\-\-all\fR [FLAVOR]\fR
gets the current status of all
jails, with optional output style [FLAVOR].
Flavors: 'basic' (default), 'cymru', 'short', 'stats'.
jails, with optional output style
[FLAVOR]. Flavors: 'basic'
(default), 'cymru', 'short',
\&'stats'
.TP
\fBstat[istic]s\fR
gets the current statistics of all
@ -214,9 +216,11 @@ stops the jail <JAIL>. The jail is
removed
.TP
\fBstatus <JAIL> [FLAVOR]\fR
gets the current status of all
jails, with optional output style [FLAVOR].
Flavors: 'basic' (default), 'cymru', 'short', 'stats'.
gets the current status of <JAIL>,
with optional output style
[FLAVOR]. Flavors: 'basic'
(default), 'cymru', 'short',
\&'stats'
.IP
JAIL CONFIGURATION
.TP

View file

@ -1,13 +1,13 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH FAIL2BAN-PYTHON "1" "April 2024" "fail2ban-python 1.1.1.1" "User Commands"
.TH FAIL2BAN-PYTHON "1" "August 2026" "fail2ban-python 1.1.2.1" "User Commands"
.SH NAME
fail2ban-python \- a helper for Fail2Ban to assure that the same Python is used
.SH DESCRIPTION
usage: fail2ban\-python [option] ... [\-c cmd | \fB\-m\fR mod | file | \fB\-]\fR [arg] ...
Options (and corresponding environment variables):
\fB\-b\fR : issue warnings about str(bytes_instance), str(bytearray_instance)
\fB\-b\fR : issue warnings about converting bytes/bytearray to str and comparing
.IP
and comparing bytes/bytearray with str. (\fB\-bb\fR: issue errors)
bytes/bytearray with str or bytes with int. (\fB\-bb\fR: issue errors)
.PP
\fB\-B\fR : don't write .pyc files on import; also PYTHONDONTWRITEBYTECODE=x
\fB\-c\fR cmd : program passed in as string (terminates option list)
@ -21,7 +21,7 @@ debug builds); also PYTHONDEBUG=x
.IP
if stdin does not appear to be a terminal; also PYTHONINSPECT=x
.PP
\fB\-I\fR : isolate Python from the user's environment (implies \fB\-E\fR and \fB\-s\fR)
\fB\-I\fR : isolate Python from the user's environment (implies \fB\-E\fR, \fB\-P\fR and \fB\-s\fR)
\fB\-m\fR mod : run library module as a script (terminates option list)
\fB\-O\fR : remove assert and __debug__\-dependent statements; add .opt\-1 before
.IP
@ -31,9 +31,12 @@ if stdin does not appear to be a terminal; also PYTHONINSPECT=x
.IP
\&.pyc extension
.PP
\fB\-P\fR : don't prepend a potentially unsafe path to sys.path
\fB\-P\fR : don't prepend a potentially unsafe path to sys.path; also
.IP
PYTHONSAFEPATH
.PP
\fB\-q\fR : don't print version and copyright messages on interactive startup
\fB\-s\fR : don't add user site directory to sys.path; also PYTHONNOUSERSITE
\fB\-s\fR : don't add user site directory to sys.path; also PYTHONNOUSERSITE=x
\fB\-S\fR : don't imply 'import site' on initialization
\fB\-u\fR : force the stdout and stderr streams to be unbuffered;
.IP
@ -57,9 +60,10 @@ also PYTHONWARNINGS=arg
.IP
control how Python invalidates hash\-based .pyc files
.PP
\fB\-\-help\-env\fR : print help about Python environment variables and exit
\fB\-\-help\-xoptions\fR : print help about implementation\-specific \fB\-X\fR options and exit
\fB\-\-help\-all\fR : print complete help information and exit
\fB\-\-help\-env\fR: print help about Python environment variables and exit
\fB\-\-help\-xoptions\fR: print help about implementation\-specific \fB\-X\fR options and exit
\fB\-\-help\-all\fR: print complete help information and exit
.PP
Arguments:
file : program read from script file
\- : program read from stdin (default; interactive mode if a tty)

View file

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH FAIL2BAN-REGEX "1" "June 2025" "fail2ban-regex 1.1.1.dev1" "User Commands"
.TH FAIL2BAN-REGEX "1" "August 2026" "fail2ban-regex 1.1.2.dev1" "User Commands"
.SH NAME
fail2ban-regex \- test Fail2ban "failregex" option
.SH SYNOPSIS

View file

@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH FAIL2BAN-SERVER "1" "April 2024" "Fail2Ban v1.1.1.dev1" "User Commands"
.TH FAIL2BAN-SERVER "1" "August 2026" "Fail2Ban v1.1.2.dev1" "User Commands"
.SH NAME
fail2ban-server \- start the server
.SH SYNOPSIS
.B fail2ban-server
[\fI\,OPTIONS\/\fR]
.SH DESCRIPTION
Fail2Ban v1.1.1.dev1 reads log file that contains password failure report
Fail2Ban v1.1.2.dev1 reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules.
.SH OPTIONS
.TP

View file

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH FAIL2BAN-TESTCASES "1" "April 2024" "fail2ban-testcases 1.1.1.dev1" "User Commands"
.TH FAIL2BAN-TESTCASES "1" "August 2026" "fail2ban-testcases 1.1.2.dev1" "User Commands"
.SH NAME
fail2ban-testcases \- run Fail2Ban unit-tests
.SH SYNOPSIS

View file

@ -80,7 +80,7 @@ class install_scripts_f2b(install_scripts):
except: # pragma: no cover
print('WARNING: Cannot find root-base option, check the bin-path to fail2ban-scripts in "fail2ban.service" and "fail2ban-openrc.init".')
scripts = ['fail2ban.service', 'fail2ban-openrc.init']
scripts = ['fail2ban.service', 'fail2ban.socket', 'fail2ban-openrc.init']
for script in scripts:
print(('Creating %s/%s (from %s.in): @BINDIR@ -> %s' % (buildroot, script, script, install_dir)))
with open(os.path.join(source_dir, 'files/%s.in' % script), 'r') as fn:
@ -294,5 +294,6 @@ if sys.argv[1] == "install":
print("They are in \"/etc/fail2ban/\".")
print("")
print("You can also install systemd service-unit file from \"build/fail2ban.service\"")
print("(optionally paired with \"build/fail2ban.socket\" to enable socket activation)")
print("resp. corresponding init script from \"files/*-initd\".")
print("")