From 68f827e1f3945b735f214e15b224a926bb4bf170 Mon Sep 17 00:00:00 2001
From: sebres <serg.brester@sebres.de>
Date: Fri, 13 Mar 2020 18:03:27 +0100
Subject: [PATCH 1/4] small optimization for manually (via client / protocol)
 signaled attempt (performBan only if maxretry gets reached)

---
 fail2ban/server/filter.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/fail2ban/server/filter.py b/fail2ban/server/filter.py
index c668e77d..e7f3e01d 100644
--- a/fail2ban/server/filter.py
+++ b/fail2ban/server/filter.py
@@ -457,10 +457,10 @@ class Filter(JailThread):
 		logSys.info(
 			"[%s] Attempt %s - %s", self.jailName, ip, datetime.datetime.fromtimestamp(unixTime).strftime("%Y-%m-%d %H:%M:%S")
 		)
-		self.failManager.addFailure(ticket, len(matches) or 1)
-
+		attempts = self.failManager.addFailure(ticket, len(matches) or 1)
 		# Perform the ban if this attempt is resulted to:
-		self.performBan(ip)
+		if attempts >= self.failManager.getMaxRetry():
+			self.performBan(ip)
 
 		return 1
 

From bc2b81133c2c5a460673ddab54fe30bcc2af9ecd Mon Sep 17 00:00:00 2001
From: sebres <serg.brester@sebres.de>
Date: Fri, 13 Mar 2020 22:07:32 +0100
Subject: [PATCH 2/4] pyinotify backend: guarantees initial scanning of
 log-file by start (retarded via pending event if filter not yet active)

---
 ChangeLog                          | 1 +
 fail2ban/server/filterpyinotify.py | 8 +++++++-
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/ChangeLog b/ChangeLog
index 6a6b4451..3781f467 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -34,6 +34,7 @@ ver. 0.10.6-dev (20??/??/??) - development edition
 ### Fixes
 * [stability] prevent race condition - no ban if filter (backend) is continuously busy if
   too many messages will be found in log, e. g. initial scan of large log-file or journal (gh-2660)
+* pyinotify-backend sporadically avoided initial scanning of log-file by start
 * python 3.9 compatibility (and Travis CI support)
 * restoring a large number (500+ depending on files ulimit) of current bans when using PyPy fixed
 * manual ban is written to database, so can be restored by restart (gh-2647)
diff --git a/fail2ban/server/filterpyinotify.py b/fail2ban/server/filterpyinotify.py
index 185305ca..6d0172da 100644
--- a/fail2ban/server/filterpyinotify.py
+++ b/fail2ban/server/filterpyinotify.py
@@ -271,7 +271,13 @@ class FilterPyinotify(FileFilter):
 
 	def _addLogPath(self, path):
 		self._addFileWatcher(path)
-		self._process_file(path)
+		# initial scan:
+		if self.active:
+			# we can execute it right now:
+			self._process_file(path)
+		else:
+			# retard until filter gets started:
+			self._addPending(path, ('INITIAL', path))
 
     ##
 	# Delete a log path

From b43dc147b5177019a1dcb51de9833139e904cc21 Mon Sep 17 00:00:00 2001
From: sebres <serg.brester@sebres.de>
Date: Fri, 13 Mar 2020 22:20:01 +0100
Subject: [PATCH 3/4] amend to RC-fix 9f1c6f1617018a0e00fc8bf7bfd62db2c17fa11a
 (gh-2660): resolves bottleneck by initial scanning of a lot of messages (or
 evildoers generating many messages) causes repeated ban, that will be ignored
 but could cause entering of "long" sleep in actions thread previously;
 speedup recognition banning queue has entries to begin check-ban process in
 actions thread

---
 fail2ban/server/actions.py  | 10 ++++++----
 fail2ban/server/jail.py     |  6 ++++++
 fail2ban/tests/dummyjail.py |  4 ++++
 3 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/fail2ban/server/actions.py b/fail2ban/server/actions.py
index d1f46ac0..902d7aa6 100644
--- a/fail2ban/server/actions.py
+++ b/fail2ban/server/actions.py
@@ -318,8 +318,10 @@ class Actions(JailThread, Mapping):
 				logSys.debug("Actions: leave idle mode")
 				continue
 			# wait for ban (stop if gets inactive):
-			bancnt = Utils.wait_for(lambda: not self.active or self.__checkBan(), self.sleeptime)
-			cnt += bancnt
+			bancnt = 0
+			if Utils.wait_for(lambda: not self.active or self._jail.hasFailTickets, self.sleeptime):
+				bancnt = self.__checkBan()
+				cnt += bancnt
 			# unban if nothing is banned not later than banned tickets >= banPrecedence
 			if not bancnt or cnt >= self.banPrecedence:
 				if self.active:
@@ -495,8 +497,8 @@ class Actions(JailThread, Mapping):
 						cnt += self.__reBan(bTicket, actions=rebanacts)
 				else: # pragma: no cover - unexpected: ticket is not banned for some reasons - reban using all actions:
 					cnt += self.__reBan(bTicket)
-			# add ban to database:
-			if not bTicket.restored and self._jail.database is not None:
+			# add ban to database (and ignore too old tickets, replace it with inOperation later):
+			if not bTicket.restored and self._jail.database is not None and bTicket.getTime() >= MyTime.time() - 60:
 				self._jail.database.addBan(self._jail, bTicket)
 		if cnt:
 			logSys.debug("Banned %s / %s, %s ticket(s) in %r", cnt, 
diff --git a/fail2ban/server/jail.py b/fail2ban/server/jail.py
index b8a1c1f4..048aded9 100644
--- a/fail2ban/server/jail.py
+++ b/fail2ban/server/jail.py
@@ -191,6 +191,12 @@ class Jail(object):
 			("Actions", self.actions.status(flavor=flavor)),
 			]
 
+	@property
+	def hasFailTickets(self):
+		"""Retrieve whether queue has tickets to ban.
+		"""
+		return not self.__queue.empty()
+
 	def putFailTicket(self, ticket):
 		"""Add a fail ticket to the jail.
 
diff --git a/fail2ban/tests/dummyjail.py b/fail2ban/tests/dummyjail.py
index 9e9aaeed..eaa4a564 100644
--- a/fail2ban/tests/dummyjail.py
+++ b/fail2ban/tests/dummyjail.py
@@ -49,6 +49,10 @@ class DummyJail(Jail):
 		with self.lock:
 			return bool(self.queue)
 
+	@property
+	def hasFailTickets(self):
+		return bool(self.queue)
+
 	def putFailTicket(self, ticket):
 		with self.lock:
 			self.queue.append(ticket)

From b64a435b0eac0a1298235e320f1450382a2ac9fe Mon Sep 17 00:00:00 2001
From: sebres <serg.brester@sebres.de>
Date: Fri, 13 Mar 2020 22:34:15 +0100
Subject: [PATCH 4/4] ignore only not banned old (repeated and ignored) tickets

---
 fail2ban/server/actions.py | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/fail2ban/server/actions.py b/fail2ban/server/actions.py
index 902d7aa6..35b027ae 100644
--- a/fail2ban/server/actions.py
+++ b/fail2ban/server/actions.py
@@ -497,9 +497,12 @@ class Actions(JailThread, Mapping):
 						cnt += self.__reBan(bTicket, actions=rebanacts)
 				else: # pragma: no cover - unexpected: ticket is not banned for some reasons - reban using all actions:
 					cnt += self.__reBan(bTicket)
-			# add ban to database (and ignore too old tickets, replace it with inOperation later):
-			if not bTicket.restored and self._jail.database is not None and bTicket.getTime() >= MyTime.time() - 60:
-				self._jail.database.addBan(self._jail, bTicket)
+			# add ban to database:
+			if not bTicket.restored and self._jail.database is not None:
+				# ignore too old (repeated and ignored) tickets,
+				# [todo] replace it with inOperation later (once it gets back-ported):
+				if not reason and bTicket.getTime() >= MyTime.time() - 60:
+					self._jail.database.addBan(self._jail, bTicket)
 		if cnt:
 			logSys.debug("Banned %s / %s, %s ticket(s) in %r", cnt, 
 				self.__banManager.getBanTotal(), self.__banManager.size(), self._jail.name)