diff --git a/ChangeLog b/ChangeLog
index d7848d19..8c3be67d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -7,6 +7,25 @@
Fail2Ban: Changelog
===================
+ver. 1.0.2 (2022/11/09) - finally-war-game-test-tape-not-a-nuclear-alarm
+-----------
+
+### Fixes
+* backend `systemd`: code review and several fixes:
+ - wait only if it is necessary, e. g. in operational mode and if no more entries retrieved (end of journal);
+ - ensure we give enough time after possible rotation, vacuuming or adding/removing journal files,
+ and move cursor back and forth to avoid entering dead space
+* `filter.d/named-refused.conf`:
+ - support BIND named log categories, gh-3388
+ - allow `info:` as possible error prefix too ("query (cache) denied" may occur as info)
+* `filter.d/dovecot.conf`:
+ - fixes regression introduced in gh-3210: resolve extremely long search by repeated apply of non-greedy RE-part
+ with following branches (it may be extremely slow up to infinite search depending on message), gh-3370
+ - fixes regression and matches new format in aggressive mode too (amend to gh-3210)
+
+### New Features and Enhancements
+
+
ver. 1.0.1 (2022/09/27) - energy-equals-mass-times-the-speed-of-light-squared
-----------
diff --git a/config/filter.d/dovecot.conf b/config/filter.d/dovecot.conf
index 0415ecb4..dc3ebbcd 100644
--- a/config/filter.d/dovecot.conf
+++ b/config/filter.d/dovecot.conf
@@ -7,19 +7,21 @@ before = common.conf
[Definition]
+_daemon = (?:dovecot(?:-auth)?|auth)
+
_auth_worker = (?:dovecot: )?auth(?:-worker)?
_auth_worker_info = (?:conn \w+:auth(?:-worker)? \([^\)]+\): auth(?:-worker)?<\d+>: )?
-_daemon = (?:dovecot(?:-auth)?|auth)
+_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*$
- ^(?:Aborted login|Disconnected|Remote closed connection|Client has quit the connection)(?:: (?:[^\(]+|\w+\([^\)]*\))+)? \((?: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*$
+ ^(?: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)(?::(?: [^ \(]+)+)? \((?: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 = ^(?: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-normal =
diff --git a/config/filter.d/named-refused.conf b/config/filter.d/named-refused.conf
index 6dbbbf81..798f66e6 100644
--- a/config/filter.d/named-refused.conf
+++ b/config/filter.d/named-refused.conf
@@ -30,11 +30,14 @@ __pid_re=(?:\[\d+\])
__daemon_re=\(?%(_daemon)s(?:\(\S+\))?\)?:?
__daemon_combs_re=(?:%(__pid_re)s?:\s+%(__daemon_re)s|%(__daemon_re)s%(__pid_re)s?:)
+_category = (?!error|info)[\w-]+
+_category_re = (?:%(_category)s: )?
+
# hostname daemon_id spaces
# this can be optional (for instance if we match named native log files)
-__line_prefix=(?:\s*\S+ %(__daemon_combs_re)s\s+)?
+__line_prefix=\s*(?:\S+ %(__daemon_combs_re)s\s+)?%(_category_re)s
-prefregex = ^%(__line_prefix)s(?: error:)?\s*client(?: @\S*)? #\S+(?: \([\S.]+\))?: .+\s(?:denied|\(NOTAUTH\))\s*$
+prefregex = ^%(__line_prefix)s(?:(?:error|info):\s*)?client(?: @\S*)? #\S+(?: \([\S.]+\))?: .+\s(?:denied|\(NOTAUTH\))\s*$
failregex = ^(?:view (?:internal|external): )?query(?: \(cache\))?
^zone transfer
diff --git a/fail2ban/server/filtersystemd.py b/fail2ban/server/filtersystemd.py
index bf6885ef..a83b7a13 100644
--- a/fail2ban/server/filtersystemd.py
+++ b/fail2ban/server/filtersystemd.py
@@ -312,20 +312,37 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
except OSError:
pass # Reading failure, so safe to ignore
+ wcode = journal.NOP
line = None
while self.active:
# wait for records (or for timeout in sleeptime seconds):
try:
- ## todo: find better method as wait_for to break (e.g. notify) journal.wait(self.sleeptime),
- ## don't use `journal.close()` for it, because in some python/systemd implementation it may
- ## cause abnormal program termination
- #self.__journal.wait(self.sleeptime) != journal.NOP
- ##
- ## wait for entries without sleep in intervals, because "sleeping" in journal.wait:
- if not logentry:
- Utils.wait_for(lambda: not self.active or \
- self.__journal.wait(Utils.DEFAULT_SLEEP_INTERVAL) != journal.NOP,
+ ## wait for entries using journal.wait:
+ if wcode == journal.NOP and self.inOperation:
+ ## todo: find better method as wait_for to break (e.g. notify) journal.wait(self.sleeptime),
+ ## don't use `journal.close()` for it, because in some python/systemd implementation it may
+ ## cause abnormal program termination (e. g. segfault)
+ ##
+ ## wait for entries without sleep in intervals, because "sleeping" in journal.wait,
+ ## journal.NOP is 0, so we can wait for non zero (APPEND or INVALIDATE):
+ wcode = Utils.wait_for(lambda: not self.active and journal.APPEND or \
+ self.__journal.wait(Utils.DEFAULT_SLEEP_INTERVAL),
self.sleeptime, 0.00001)
+ ## if invalidate (due to rotation, vacuuming or journal files added/removed etc):
+ if self.active and wcode == journal.INVALIDATE:
+ if self.ticks:
+ logSys.log(logging.DEBUG, "[%s] Invalidate signaled, take a little break (rotation ends)", self.jailName)
+ time.sleep(self.sleeptime * 0.25)
+ Utils.wait_for(lambda: not self.active or \
+ self.__journal.wait(Utils.DEFAULT_SLEEP_INTERVAL) != journal.INVALIDATE,
+ self.sleeptime * 3, 0.00001)
+ if self.ticks:
+ # move back and forth to ensure do not end up in dead space by rotation or vacuuming,
+ # if position beyond end of journal (gh-3396)
+ try:
+ if self.__journal.get_previous(): self.__journal.get_next()
+ except OSError:
+ pass
if self.idle:
# because journal.wait will returns immediatelly if we have records in journal,
# just wait a little bit here for not idle, to prevent hi-load:
@@ -360,11 +377,13 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
self.processLineAndAdd(line, tm)
self.__modified += 1
if self.__modified >= 100: # todo: should be configurable
+ wcode = journal.APPEND; # don't need wait - there are still unprocessed entries
break
else:
# "in operation" mode since we don't have messages anymore (reached end of journal):
if not self.inOperation:
self.inOperationMode()
+ wcode = journal.NOP; # enter wait - no more entries to process
break
self.__modified = 0
if self.ticks % 10 == 0:
@@ -384,6 +403,7 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
except Exception as e: # pragma: no cover
if not self.active: # if not active - error by stop...
break
+ wcode = journal.NOP
logSys.error("Caught unhandled exception in main cycle: %r", e,
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
# incr common error counter:
@@ -392,15 +412,20 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
logSys.debug("[%s] filter terminated", self.jailName)
# close journal:
+ self.closeJournal()
+
+ logSys.debug("[%s] filter exited (systemd)", self.jailName)
+ return True
+
+ def closeJournal(self):
try:
- if self.__journal:
- self.__journal.close()
+ jnl, self.__journal = self.__journal, None
+ if jnl:
+ jnl.close()
except Exception as e: # pragma: no cover
logSys.error("Close journal failed: %r", e,
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
- logSys.debug("[%s] filter exited (systemd)", self.jailName)
- return True
def status(self, flavor="basic"):
ret = super(FilterSystemd, self).status(flavor=flavor)
@@ -422,6 +447,8 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
def onStop(self):
"""Stop monitoring of journal. Invoked after run method.
"""
+ # close journal:
+ self.closeJournal()
# ensure positions of pending logs are up-to-date:
if self._pendDBUpdates and self.jail.database:
self._updateDBPending()
diff --git a/fail2ban/tests/fail2banregextestcase.py b/fail2ban/tests/fail2banregextestcase.py
index f29f3333..213ea89b 100644
--- a/fail2ban/tests/fail2banregextestcase.py
+++ b/fail2ban/tests/fail2banregextestcase.py
@@ -36,7 +36,7 @@ from .utils import CONFIG_DIR
fail2banregex.logSys = logSys
def _test_output(*args):
- logSys.notice(args[0])
+ logSys.notice('output: %s', args[0])
fail2banregex.output = _test_output
@@ -360,57 +360,57 @@ class Fail2banRegexTest(LogCaptureTestCase):
def testFrmtOutput(self):
# id/ip only:
self.assertTrue(_test_exec('-o', 'id', STR_00, RE_00_ID))
- self.assertLogged('kevin')
+ self.assertLogged('output: %s' % 'kevin')
self.pruneLog()
# multiple id combined to a tuple (id, tuple_id):
self.assertTrue(_test_exec('-o', 'id', '-d', '{^LN-BEG}EPOCH',
'1591983743.667 192.0.2.1 192.0.2.2',
r'^\s* \S+'))
- self.assertLogged(str(('192.0.2.1', '192.0.2.2')))
+ self.assertLogged('output: %s' % str(('192.0.2.1', '192.0.2.2')))
self.pruneLog()
# multiple id combined to a tuple, id first - (id, tuple_id_1, tuple_id_2):
self.assertTrue(_test_exec('-o', 'id', '-d', '{^LN-BEG}EPOCH',
'1591983743.667 left 192.0.2.3 right',
r'^\s*\S+ \S+'))
- self.assertLogged(str(('192.0.2.3', 'left', 'right')))
+ self.assertLogged('output: %s' % str(('192.0.2.3', 'left', 'right')))
self.pruneLog()
# id had higher precedence as ip-address:
self.assertTrue(_test_exec('-o', 'id', '-d', '{^LN-BEG}EPOCH',
'1591983743.667 left [192.0.2.4]:12345 right',
r'^\s*\S+ : \S+'))
- self.assertLogged(str(('[192.0.2.4]:12345', 'left', 'right')))
+ self.assertLogged('output: %s' % str(('[192.0.2.4]:12345', 'left', 'right')))
self.pruneLog()
# ip is not id anymore (if IP-address deviates from ID):
self.assertTrue(_test_exec('-o', 'ip', '-d', '{^LN-BEG}EPOCH',
'1591983743.667 left [192.0.2.4]:12345 right',
r'^\s*\S+ : \S+'))
- self.assertNotLogged(str(('[192.0.2.4]:12345', 'left', 'right')))
- self.assertLogged('192.0.2.4')
+ self.assertNotLogged('output: %s' % str(('[192.0.2.4]:12345', 'left', 'right')))
+ self.assertLogged('output: %s' % '192.0.2.4')
self.pruneLog()
self.assertTrue(_test_exec('-o', 'ID: | IP:', '-d', '{^LN-BEG}EPOCH',
'1591983743.667 left [192.0.2.4]:12345 right',
r'^\s*\S+ : \S+'))
- self.assertLogged('ID:'+str(('[192.0.2.4]:12345', 'left', 'right'))+' | IP:192.0.2.4')
+ self.assertLogged('output: %s' % 'ID:'+str(('[192.0.2.4]:12345', 'left', 'right'))+' | IP:192.0.2.4')
self.pruneLog()
# row with id :
self.assertTrue(_test_exec('-o', 'row', STR_00, RE_00_ID))
- self.assertLogged("['kevin'", "'ip4': '192.0.2.0'", "'fid': 'kevin'", all=True)
+ self.assertLogged('output: %s' % "['kevin'", "'ip4': '192.0.2.0'", "'fid': 'kevin'", all=True)
self.pruneLog()
# row with ip :
self.assertTrue(_test_exec('-o', 'row', STR_00, RE_00_USER))
- self.assertLogged("['192.0.2.0'", "'ip4': '192.0.2.0'", "'user': 'kevin'", all=True)
+ self.assertLogged('output: %s' % "['192.0.2.0'", "'ip4': '192.0.2.0'", "'user': 'kevin'", all=True)
self.pruneLog()
# log msg :
self.assertTrue(_test_exec('-o', 'msg', STR_00, RE_00_USER))
- self.assertLogged(STR_00)
+ self.assertLogged('output: %s' % STR_00)
self.pruneLog()
# item of match (user):
self.assertTrue(_test_exec('-o', 'user', STR_00, RE_00_USER))
- self.assertLogged('kevin')
+ self.assertLogged('output: %s' % 'kevin')
self.pruneLog()
# complex substitution using tags (ip, user, family):
self.assertTrue(_test_exec('-o', ', , ', STR_00, RE_00_USER))
- self.assertLogged('192.0.2.0, kevin, inet4')
+ self.assertLogged('output: %s' % '192.0.2.0, kevin, inet4')
self.pruneLog()
def testStalledIPByNoFailFrmtOutput(self):
diff --git a/fail2ban/tests/files/logs/dovecot b/fail2ban/tests/files/logs/dovecot
index 75934c37..0e332961 100644
--- a/fail2ban/tests/files/logs/dovecot
+++ b/fail2ban/tests/files/logs/dovecot
@@ -115,6 +115,17 @@ Aug 28 06:38:51 s166-62-100-187 dovecot: imap-login: Disconnected (auth failed,
# failJSON: { "time": "2004-08-28T06:38:52", "match": true , "host": "192.0.2.4", "desc": "open parenthesis in optional part between Disconnected and (auth failed ...), gh-3210" }
Aug 28 06:38:52 s166-62-100-187 dovecot: imap-login: Disconnected: Connection closed: read(size=1003) failed: Connection reset by peer (auth failed, 1 attempts in 0 secs): user=, rip=192.0.2.4, lip=127.0.0.19, session=
+# failJSON: { "time": "2004-08-29T01:49:33", "match": false , "desc": "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-29T01:49:33", "match": false , "desc": "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": false , "desc": "avoid slow RE, gh-3370" }
+Aug 29 01:49:33 server dovecot[459]: managesieve-login: Disconnected: Too many invalid commands. (no auth attempts in 0 secs): user=<>, rip=192.0.2.5, lip=127.0.0.1
+# failJSON: { "time": "2004-08-29T01:49:33", "match": false , "desc": "avoid slow RE, gh-3370" }
+Aug 29 01:49:33 server dovecot[459]: managesieve-login: Disconnected: Connection closed: read(size=1007) failed: Connection reset by peer (no auth attempts in 1 secs): user=<>, rip=192.0.2.5, lip=127.0.0.1
+# failJSON: { "time": "2004-08-29T01:49:33", "match": false , "desc": "avoid slow RE, gh-3370" }
+Aug 29 01:49:33 server dovecot[472]: imap-login: Disconnected: Connection closed: SSL_accept() failed: error:14209102:SSL routines:tls_early_post_process_client_hello:unsupported protocol (no auth attempts in 0 secs): user=<>, rip=192.0.2.5, lip=127.0.0.1, TLS handshaking: SSL_accept() failed: error:14209102:SSL routines:tls_early_post_process_client_hello:unsupported protocol
+
# failJSON: { "time": "2004-08-29T03:17:18", "match": true , "host": "192.0.2.133" }
Aug 29 03:17:18 server dovecot: submission-login: Client has quit the connection (auth failed, 1 attempts in 2 secs): user=, method=LOGIN, rip=192.0.2.133, lip=0.0.0.0
# failJSON: { "time": "2004-08-29T03:53:52", "match": true , "host": "192.0.2.169" }
@@ -128,6 +139,17 @@ Aug 29 15:33:53 server dovecot: managesieve-login: Disconnected: Too many invali
# filterOptions: [{"mode": "aggressive"}]
+# 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-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" }
+Aug 29 01:49:33 server dovecot[459]: managesieve-login: Disconnected: Too many invalid commands. (no auth attempts in 0 secs): user=<>, rip=192.0.2.5, lip=127.0.0.1
+# 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]: managesieve-login: Disconnected: Connection closed: read(size=1007) failed: Connection reset by peer (no auth attempts in 1 secs): user=<>, rip=192.0.2.5, lip=127.0.0.1
+# 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[472]: imap-login: Disconnected: Connection closed: SSL_accept() failed: error:14209102:SSL routines:tls_early_post_process_client_hello:unsupported protocol (no auth attempts in 0 secs): user=<>, rip=192.0.2.5, lip=127.0.0.1, TLS handshaking: SSL_accept() failed: error:14209102:SSL routines:tls_early_post_process_client_hello:unsupported protocol
+
# failJSON: { "time": "2004-08-29T16:06:58", "match": true , "host": "192.0.2.5" }
Aug 29 16:06:58 s166-62-100-187 dovecot: imap-login: Disconnected (disconnected before auth was ready, waited 0 secs): user=<>, rip=192.0.2.5, lip=192.168.1.2, TLS handshaking: SSL_accept() syscall failed: Connection reset by peer
# failJSON: { "time": "2004-08-31T16:15:10", "match": true , "host": "192.0.2.6" }
diff --git a/fail2ban/tests/files/logs/named-refused b/fail2ban/tests/files/logs/named-refused
index c06a4146..5ef42074 100644
--- a/fail2ban/tests/files/logs/named-refused
+++ b/fail2ban/tests/files/logs/named-refused
@@ -27,6 +27,11 @@ Aug 27 16:58:31 vhost1-ua named[29206]: client 176.9.92.38#42592 (simmarket.com.
# failJSON: { "time": "2004-08-27T16:59:00", "match": true , "host": "192.0.2.1", "desc": "new log format, 9.11.0 (#2406)" }
Aug 27 16:59:00 host named[28098]: client @0x7f6450002ef0 192.0.2.1#23332 (example.com): bad zone transfer request: 'test.com/IN': non-authoritative zone (NOTAUTH)
+# failJSON: { "match": true , "host": "192.0.2.8", "desc": "log message with category (security), gh-3388" }
+Oct 23 02:06:39 security: info: client @0x7f4e446fd6e8 192.0.2.8#53 (example.io): query (cache) 'example.io/A/IN' denied
+# failJSON: { "match": true , "host": "192.0.2.237", "desc": "log message with category, gh-3388" }
+Oct 23 03:35:40 update-security: error: client @0x7f4e45c07a48 192.0.2.237#55956 (example.ca): zone transfer 'example.ca/AXFR/IN' denied
+
# filterOptions: {"logtype": "journal"}
# failJSON: { "match": true , "host": "192.0.2.1", "desc": "systemd-journal entry" }
diff --git a/fail2ban/version.py b/fail2ban/version.py
index d08b3b10..25ac2284 100644
--- a/fail2ban/version.py
+++ b/fail2ban/version.py
@@ -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.0.1"
+version = "1.0.2"
def normVersion():
""" Returns fail2ban version in normalized machine-readable format"""
diff --git a/man/fail2ban-client.1 b/man/fail2ban-client.1
index 549d3124..e4d2f44c 100644
--- a/man/fail2ban-client.1
+++ b/man/fail2ban-client.1
@@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.48.1.
-.TH FAIL2BAN-CLIENT "1" "September 2022" "Fail2Ban v1.0.1" "User Commands"
+.TH FAIL2BAN-CLIENT "1" "November 2022" "Fail2Ban v1.0.2" "User Commands"
.SH NAME
fail2ban-client \- configure and control the server
.SH SYNOPSIS
.B fail2ban-client
[\fI\,OPTIONS\/\fR] \fI\,\/\fR
.SH DESCRIPTION
-Fail2Ban v1.0.1 reads log file that contains password failure report
+Fail2Ban v1.0.2 reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules.
.SH OPTIONS
.TP
diff --git a/man/fail2ban-python.1 b/man/fail2ban-python.1
index eddbe7ab..225c8295 100644
--- a/man/fail2ban-python.1
+++ b/man/fail2ban-python.1
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.48.1.
-.TH FAIL2BAN-PYTHON "1" "September 2022" "fail2ban-python 1.0.1" "User Commands"
+.TH FAIL2BAN-PYTHON "1" "November 2022" "fail2ban-python 1.0.2" "User Commands"
.SH NAME
fail2ban-python \- a helper for Fail2Ban to assure that the same Python is used
.SH DESCRIPTION
diff --git a/man/fail2ban-regex.1 b/man/fail2ban-regex.1
index d008d421..5e64ef5b 100644
--- a/man/fail2ban-regex.1
+++ b/man/fail2ban-regex.1
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.48.1.
-.TH FAIL2BAN-REGEX "1" "September 2022" "fail2ban-regex 1.0.1" "User Commands"
+.TH FAIL2BAN-REGEX "1" "November 2022" "fail2ban-regex 1.0.2" "User Commands"
.SH NAME
fail2ban-regex \- test Fail2ban "failregex" option
.SH SYNOPSIS
diff --git a/man/fail2ban-server.1 b/man/fail2ban-server.1
index a0204e1b..ad1d84de 100644
--- a/man/fail2ban-server.1
+++ b/man/fail2ban-server.1
@@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.48.1.
-.TH FAIL2BAN-SERVER "1" "September 2022" "Fail2Ban v1.0.1" "User Commands"
+.TH FAIL2BAN-SERVER "1" "November 2022" "Fail2Ban v1.0.2" "User Commands"
.SH NAME
fail2ban-server \- start the server
.SH SYNOPSIS
.B fail2ban-server
[\fI\,OPTIONS\/\fR]
.SH DESCRIPTION
-Fail2Ban v1.0.1 reads log file that contains password failure report
+Fail2Ban v1.0.2 reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules.
.SH OPTIONS
.TP
diff --git a/man/fail2ban-testcases.1 b/man/fail2ban-testcases.1
index addc5de4..7221c0cd 100644
--- a/man/fail2ban-testcases.1
+++ b/man/fail2ban-testcases.1
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.48.1.
-.TH FAIL2BAN-TESTCASES "1" "September 2022" "fail2ban-testcases 1.0.1" "User Commands"
+.TH FAIL2BAN-TESTCASES "1" "November 2022" "fail2ban-testcases 1.0.2" "User Commands"
.SH NAME
fail2ban-testcases \- run Fail2Ban unit-tests
.SH SYNOPSIS