summaryrefslogtreecommitdiff
path: root/fail2ban/server
diff options
context:
space:
mode:
authorsebres <serg.brester@sebres.de>2014-09-23 19:57:55 +0200
committersebres <serg.brester@sebres.de>2014-10-29 12:36:21 +0100
commit518cc92ccc0866c1b82bedbf8b48f9a603b5c737 (patch)
treedaa18ba9a2668ab3782f67e782246b7e2eda1f81 /fail2ban/server
parent7acddcbe4afd1df547da151414cbe4f245d60ca1 (diff)
downloadfail2ban-518cc92ccc0866c1b82bedbf8b48f9a603b5c737.tar.gz
actions: bug fix in lambdas in checkBan, because getBansMerged could return None (purge resp. asynchronous addBan), make the logic all around more stable;
test cases: extended with test to check action together with database functionality (ex.: to verify lambdas in checkBan); database: getBansMerged should work within lock, using reentrant lock (cause call of getBans inside of getBansMerged);
Diffstat (limited to 'fail2ban/server')
-rw-r--r--fail2ban/server/actions.py51
-rw-r--r--fail2ban/server/database.py77
2 files changed, 84 insertions, 44 deletions
diff --git a/fail2ban/server/actions.py b/fail2ban/server/actions.py
index c8e9c5d9..a36802bc 100644
--- a/fail2ban/server/actions.py
+++ b/fail2ban/server/actions.py
@@ -243,6 +243,44 @@ class Actions(JailThread, Mapping):
logSys.debug(self._jail.name + ": action terminated")
return True
+ def __getBansMerged(self, mi, idx):
+ """Helper for lamda to get bans merged once
+
+ This function never returns None for ainfo lambdas - always a ticket (merged or single one)
+ and prevents any errors through merging (to guarantee ban actions will be executed).
+ [TODO] move merging to observer - here we could wait for merge and read already merged info from a database
+
+ Parameters
+ ----------
+ mi : dict
+ initial for lambda should contains {ip, ticket}
+ idx : str
+ key to get a merged bans :
+ 'all' - bans merged for all jails
+ 'jail' - bans merged for current jail only
+
+ Returns
+ -------
+ BanTicket
+ merged or self ticket only
+ """
+ if idx in mi:
+ return mi[idx] if mi[idx] is not None else mi['ticket']
+ try:
+ jail=self._jail
+ ip=mi['ip']
+ mi[idx] = None
+ if idx == 'all':
+ mi[idx] = jail.database.getBansMerged(ip=ip)
+ elif idx == 'jail':
+ mi[idx] = jail.database.getBansMerged(ip=ip, jail=jail)
+ except Exception as e:
+ logSys.error(
+ "Failed to get %s bans merged, jail '%s': %s",
+ idx, jail.name, e,
+ exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
+ return mi[idx] if mi[idx] is not None else mi['ticket']
+
def __checkBan(self):
"""Check for IP address to ban.
@@ -264,14 +302,11 @@ class Actions(JailThread, Mapping):
aInfo["time"] = bTicket.getTime()
aInfo["matches"] = "\n".join(bTicket.getMatches())
if self._jail.database is not None:
- aInfo["ipmatches"] = lambda jail=self._jail: "\n".join(
- jail.database.getBansMerged(ip=ip).getMatches())
- aInfo["ipjailmatches"] = lambda jail=self._jail: "\n".join(
- jail.database.getBansMerged(ip=ip, jail=jail).getMatches())
- aInfo["ipfailures"] = lambda jail=self._jail: \
- jail.database.getBansMerged(ip=ip).getAttempt()
- aInfo["ipjailfailures"] = lambda jail=self._jail: \
- jail.database.getBansMerged(ip=ip, jail=jail).getAttempt()
+ mi4ip = lambda idx, self=self, mi={'ip':ip, 'ticket':bTicket}: self.__getBansMerged(mi, idx)
+ aInfo["ipmatches"] = lambda: "\n".join(mi4ip('all').getMatches())
+ aInfo["ipjailmatches"] = lambda: "\n".join(mi4ip('jail').getMatches())
+ aInfo["ipfailures"] = lambda: mi4ip('all').getAttempt()
+ aInfo["ipjailfailures"] = lambda: mi4ip('jail').getAttempt()
if self.__banManager.addBanTicket(bTicket):
logSys.notice("[%s] Ban %s" % (self._jail.name, aInfo["ip"]))
for name, action in self._actions.iteritems():
diff --git a/fail2ban/server/database.py b/fail2ban/server/database.py
index 351d1829..bbf7adad 100644
--- a/fail2ban/server/database.py
+++ b/fail2ban/server/database.py
@@ -27,7 +27,7 @@ import sqlite3
import json
import locale
from functools import wraps
-from threading import Lock
+from threading import RLock
from .mytime import MyTime
from .ticket import FailTicket
@@ -123,7 +123,7 @@ class Fail2BanDb(object):
def __init__(self, filename, purgeAge=24*60*60):
try:
- self._lock = Lock()
+ self._lock = RLock()
self._db = sqlite3.connect(
filename, check_same_thread=False,
detect_types=sqlite3.PARSE_DECLTYPES)
@@ -365,6 +365,10 @@ class Fail2BanDb(object):
del self._bansMergedCache[(ticket.getIP(), jail)]
except KeyError:
pass
+ try:
+ del self._bansMergedCache[(ticket.getIP(), None)]
+ except KeyError:
+ pass
#TODO: Implement data parts once arbitrary match keys completed
cur.execute(
"INSERT INTO bans(jail, ip, timeofban, data) VALUES(?, ?, ?, ?)",
@@ -455,40 +459,41 @@ class Fail2BanDb(object):
in a list. When `ip` argument passed, a single `Ticket` is
returned.
"""
- cacheKey = None
- if bantime is None or bantime < 0:
- cacheKey = (ip, jail)
- if cacheKey in self._bansMergedCache:
- return self._bansMergedCache[cacheKey]
-
- tickets = []
- ticket = None
-
- results = list(self._getBans(ip=ip, jail=jail, bantime=bantime))
- if results:
- prev_banip = results[0][0]
- matches = []
- failures = 0
- for banip, timeofban, data in results:
- #TODO: Implement data parts once arbitrary match keys completed
- if banip != prev_banip:
- ticket = FailTicket(prev_banip, prev_timeofban, matches)
- ticket.setAttempt(failures)
- tickets.append(ticket)
- # Reset variables
- prev_banip = banip
- matches = []
- failures = 0
- matches.extend(data['matches'])
- failures += data['failures']
- prev_timeofban = timeofban
- ticket = FailTicket(banip, prev_timeofban, matches)
- ticket.setAttempt(failures)
- tickets.append(ticket)
-
- if cacheKey:
- self._bansMergedCache[cacheKey] = tickets if ip is None else ticket
- return tickets if ip is None else ticket
+ with self._lock:
+ cacheKey = None
+ if bantime is None or bantime < 0:
+ cacheKey = (ip, jail)
+ if cacheKey in self._bansMergedCache:
+ return self._bansMergedCache[cacheKey]
+
+ tickets = []
+ ticket = None
+
+ results = list(self._getBans(ip=ip, jail=jail, bantime=bantime))
+ if results:
+ prev_banip = results[0][0]
+ matches = []
+ failures = 0
+ for banip, timeofban, data in results:
+ #TODO: Implement data parts once arbitrary match keys completed
+ if banip != prev_banip:
+ ticket = FailTicket(prev_banip, prev_timeofban, matches)
+ ticket.setAttempt(failures)
+ tickets.append(ticket)
+ # Reset variables
+ prev_banip = banip
+ matches = []
+ failures = 0
+ matches.extend(data['matches'])
+ failures += data['failures']
+ prev_timeofban = timeofban
+ ticket = FailTicket(banip, prev_timeofban, matches)
+ ticket.setAttempt(failures)
+ tickets.append(ticket)
+
+ if cacheKey:
+ self._bansMergedCache[cacheKey] = tickets if ip is None else ticket
+ return tickets if ip is None else ticket
@commitandrollback
def purge(self, cur):