|
|
@ -104,7 +104,11 @@ def commitandrollback(f):
|
|
|
|
def wrapper(self, *args, **kwargs):
|
|
|
|
def wrapper(self, *args, **kwargs):
|
|
|
|
with self._lock: # Threading lock
|
|
|
|
with self._lock: # Threading lock
|
|
|
|
with self._db: # Auto commit and rollback on exception
|
|
|
|
with self._db: # Auto commit and rollback on exception
|
|
|
|
return f(self, self._db.cursor(), *args, **kwargs)
|
|
|
|
cur = self._db.cursor()
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
return f(self, cur, *args, **kwargs)
|
|
|
|
|
|
|
|
finally:
|
|
|
|
|
|
|
|
cur.close()
|
|
|
|
return wrapper
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -253,7 +257,7 @@ class Fail2BanDb(object):
|
|
|
|
self.repairDB()
|
|
|
|
self.repairDB()
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
version = cur.fetchone()[0]
|
|
|
|
version = cur.fetchone()[0]
|
|
|
|
if version < Fail2BanDb.__version__:
|
|
|
|
if version != Fail2BanDb.__version__:
|
|
|
|
newversion = self.updateDb(version)
|
|
|
|
newversion = self.updateDb(version)
|
|
|
|
if newversion == Fail2BanDb.__version__:
|
|
|
|
if newversion == Fail2BanDb.__version__:
|
|
|
|
logSys.warning( "Database updated from '%r' to '%r'",
|
|
|
|
logSys.warning( "Database updated from '%r' to '%r'",
|
|
|
@ -301,9 +305,11 @@ class Fail2BanDb(object):
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
# backup
|
|
|
|
# backup
|
|
|
|
logSys.info("Trying to repair database %s", self._dbFilename)
|
|
|
|
logSys.info("Trying to repair database %s", self._dbFilename)
|
|
|
|
shutil.move(self._dbFilename, self._dbBackupFilename)
|
|
|
|
if not os.path.isfile(self._dbBackupFilename):
|
|
|
|
logSys.info(" Database backup created: %s", self._dbBackupFilename)
|
|
|
|
shutil.move(self._dbFilename, self._dbBackupFilename)
|
|
|
|
|
|
|
|
logSys.info(" Database backup created: %s", self._dbBackupFilename)
|
|
|
|
|
|
|
|
elif os.path.isfile(self._dbFilename):
|
|
|
|
|
|
|
|
os.remove(self._dbFilename)
|
|
|
|
# first try to repair using dump/restore in order
|
|
|
|
# first try to repair using dump/restore in order
|
|
|
|
Utils.executeCmd((r"""f2b_db=$0; f2b_dbbk=$1; sqlite3 "$f2b_dbbk" ".dump" | sqlite3 "$f2b_db" """,
|
|
|
|
Utils.executeCmd((r"""f2b_db=$0; f2b_dbbk=$1; sqlite3 "$f2b_dbbk" ".dump" | sqlite3 "$f2b_db" """,
|
|
|
|
self._dbFilename, self._dbBackupFilename))
|
|
|
|
self._dbFilename, self._dbBackupFilename))
|
|
|
@ -415,7 +421,7 @@ class Fail2BanDb(object):
|
|
|
|
logSys.error("Failed to upgrade database '%s': %s",
|
|
|
|
logSys.error("Failed to upgrade database '%s': %s",
|
|
|
|
self._dbFilename, e.args[0],
|
|
|
|
self._dbFilename, e.args[0],
|
|
|
|
exc_info=logSys.getEffectiveLevel() <= 10)
|
|
|
|
exc_info=logSys.getEffectiveLevel() <= 10)
|
|
|
|
raise
|
|
|
|
self.repairDB()
|
|
|
|
|
|
|
|
|
|
|
|
@commitandrollback
|
|
|
|
@commitandrollback
|
|
|
|
def addJail(self, cur, jail):
|
|
|
|
def addJail(self, cur, jail):
|
|
|
@ -789,7 +795,6 @@ class Fail2BanDb(object):
|
|
|
|
queryArgs.append(fromtime)
|
|
|
|
queryArgs.append(fromtime)
|
|
|
|
if overalljails or jail is None:
|
|
|
|
if overalljails or jail is None:
|
|
|
|
query += " GROUP BY ip ORDER BY timeofban DESC LIMIT 1"
|
|
|
|
query += " GROUP BY ip ORDER BY timeofban DESC LIMIT 1"
|
|
|
|
cur = self._db.cursor()
|
|
|
|
|
|
|
|
# repack iterator as long as in lock:
|
|
|
|
# repack iterator as long as in lock:
|
|
|
|
return list(cur.execute(query, queryArgs))
|
|
|
|
return list(cur.execute(query, queryArgs))
|
|
|
|
|
|
|
|
|
|
|
@ -812,11 +817,9 @@ class Fail2BanDb(object):
|
|
|
|
query += " GROUP BY ip ORDER BY ip, timeofban DESC"
|
|
|
|
query += " GROUP BY ip ORDER BY ip, timeofban DESC"
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
query += " ORDER BY timeofban DESC LIMIT 1"
|
|
|
|
query += " ORDER BY timeofban DESC LIMIT 1"
|
|
|
|
cur = self._db.cursor()
|
|
|
|
|
|
|
|
return cur.execute(query, queryArgs)
|
|
|
|
return cur.execute(query, queryArgs)
|
|
|
|
|
|
|
|
|
|
|
|
@commitandrollback
|
|
|
|
def getCurrentBans(self, jail=None, ip=None, forbantime=None, fromtime=None,
|
|
|
|
def getCurrentBans(self, cur, jail=None, ip=None, forbantime=None, fromtime=None,
|
|
|
|
|
|
|
|
correctBanTime=True, maxmatches=None
|
|
|
|
correctBanTime=True, maxmatches=None
|
|
|
|
):
|
|
|
|
):
|
|
|
|
"""Reads tickets (with merged info) currently affected from ban from the database.
|
|
|
|
"""Reads tickets (with merged info) currently affected from ban from the database.
|
|
|
@ -828,57 +831,63 @@ class Fail2BanDb(object):
|
|
|
|
(and therefore endOfBan) of the ticket (normally it is ban-time of jail as maximum)
|
|
|
|
(and therefore endOfBan) of the ticket (normally it is ban-time of jail as maximum)
|
|
|
|
for all tickets with ban-time greater (or persistent).
|
|
|
|
for all tickets with ban-time greater (or persistent).
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
if fromtime is None:
|
|
|
|
cur = self._db.cursor()
|
|
|
|
fromtime = MyTime.time()
|
|
|
|
try:
|
|
|
|
tickets = []
|
|
|
|
if fromtime is None:
|
|
|
|
ticket = None
|
|
|
|
fromtime = MyTime.time()
|
|
|
|
if correctBanTime is True:
|
|
|
|
tickets = []
|
|
|
|
correctBanTime = jail.getMaxBanTime() if jail is not None else None
|
|
|
|
ticket = None
|
|
|
|
# don't change if persistent allowed:
|
|
|
|
if correctBanTime is True:
|
|
|
|
if correctBanTime == -1: correctBanTime = None
|
|
|
|
correctBanTime = jail.getMaxBanTime() if jail is not None else None
|
|
|
|
|
|
|
|
# don't change if persistent allowed:
|
|
|
|
for ticket in self._getCurrentBans(cur, jail=jail, ip=ip,
|
|
|
|
if correctBanTime == -1: correctBanTime = None
|
|
|
|
forbantime=forbantime, fromtime=fromtime
|
|
|
|
|
|
|
|
):
|
|
|
|
with self._lock:
|
|
|
|
# can produce unpack error (database may return sporadical wrong-empty row):
|
|
|
|
bans = self._getCurrentBans(cur, jail=jail, ip=ip,
|
|
|
|
try:
|
|
|
|
forbantime=forbantime, fromtime=fromtime
|
|
|
|
banip, timeofban, bantime, bancount, data = ticket
|
|
|
|
)
|
|
|
|
# additionally check for empty values:
|
|
|
|
for ticket in bans:
|
|
|
|
if banip is None or banip == "": # pragma: no cover
|
|
|
|
# can produce unpack error (database may return sporadical wrong-empty row):
|
|
|
|
raise ValueError('unexpected value %r' % (banip,))
|
|
|
|
try:
|
|
|
|
# if bantime unknown (after upgrade-db from earlier version), just use min known ban-time:
|
|
|
|
banip, timeofban, bantime, bancount, data = ticket
|
|
|
|
if bantime == -2: # todo: remove it in future version
|
|
|
|
# additionally check for empty values:
|
|
|
|
bantime = jail.actions.getBanTime() if jail is not None else (
|
|
|
|
if banip is None or banip == "": # pragma: no cover
|
|
|
|
correctBanTime if correctBanTime else 600)
|
|
|
|
raise ValueError('unexpected value %r' % (banip,))
|
|
|
|
elif correctBanTime and correctBanTime >= 0:
|
|
|
|
# if bantime unknown (after upgrade-db from earlier version), just use min known ban-time:
|
|
|
|
# if persistent ban (or greater as max), use current max-bantime of the jail:
|
|
|
|
if bantime == -2: # todo: remove it in future version
|
|
|
|
if bantime == -1 or bantime > correctBanTime:
|
|
|
|
bantime = jail.actions.getBanTime() if jail is not None else (
|
|
|
|
bantime = correctBanTime
|
|
|
|
correctBanTime if correctBanTime else 600)
|
|
|
|
# after correction check the end of ban again:
|
|
|
|
elif correctBanTime and correctBanTime >= 0:
|
|
|
|
if bantime != -1 and timeofban + bantime <= fromtime:
|
|
|
|
# if persistent ban (or greater as max), use current max-bantime of the jail:
|
|
|
|
# not persistent and too old - ignore it:
|
|
|
|
if bantime == -1 or bantime > correctBanTime:
|
|
|
|
logSys.debug("ignore ticket (with new max ban-time %r): too old %r <= %r, ticket: %r",
|
|
|
|
bantime = correctBanTime
|
|
|
|
bantime, timeofban + bantime, fromtime, ticket)
|
|
|
|
# after correction check the end of ban again:
|
|
|
|
|
|
|
|
if bantime != -1 and timeofban + bantime <= fromtime:
|
|
|
|
|
|
|
|
# not persistent and too old - ignore it:
|
|
|
|
|
|
|
|
logSys.debug("ignore ticket (with new max ban-time %r): too old %r <= %r, ticket: %r",
|
|
|
|
|
|
|
|
bantime, timeofban + bantime, fromtime, ticket)
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
except ValueError as e: # pragma: no cover
|
|
|
|
|
|
|
|
logSys.debug("get current bans: ignore row %r - %s", ticket, e)
|
|
|
|
continue
|
|
|
|
continue
|
|
|
|
except ValueError as e: # pragma: no cover
|
|
|
|
# logSys.debug('restore ticket %r, %r, %r', banip, timeofban, data)
|
|
|
|
logSys.debug("get current bans: ignore row %r - %s", ticket, e)
|
|
|
|
ticket = FailTicket(banip, timeofban, data=data)
|
|
|
|
continue
|
|
|
|
# filter matches if expected (current count > as maxmatches specified):
|
|
|
|
# logSys.debug('restore ticket %r, %r, %r', banip, timeofban, data)
|
|
|
|
if maxmatches is None:
|
|
|
|
ticket = FailTicket(banip, timeofban, data=data)
|
|
|
|
maxmatches = self.maxMatches
|
|
|
|
# filter matches if expected (current count > as maxmatches specified):
|
|
|
|
if maxmatches:
|
|
|
|
if maxmatches is None:
|
|
|
|
matches = ticket.getMatches()
|
|
|
|
maxmatches = self.maxMatches
|
|
|
|
if matches and len(matches) > maxmatches:
|
|
|
|
if maxmatches:
|
|
|
|
ticket.setMatches(matches[-maxmatches:])
|
|
|
|
matches = ticket.getMatches()
|
|
|
|
else:
|
|
|
|
if matches and len(matches) > maxmatches:
|
|
|
|
ticket.setMatches(None)
|
|
|
|
ticket.setMatches(matches[-maxmatches:])
|
|
|
|
# logSys.debug('restored ticket: %r', ticket)
|
|
|
|
else:
|
|
|
|
ticket.setBanTime(bantime)
|
|
|
|
ticket.setMatches(None)
|
|
|
|
ticket.setBanCount(bancount)
|
|
|
|
# logSys.debug('restored ticket: %r', ticket)
|
|
|
|
if ip is not None: return ticket
|
|
|
|
ticket.setBanTime(bantime)
|
|
|
|
tickets.append(ticket)
|
|
|
|
ticket.setBanCount(bancount)
|
|
|
|
finally:
|
|
|
|
if ip is not None: return ticket
|
|
|
|
cur.close()
|
|
|
|
tickets.append(ticket)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return tickets
|
|
|
|
return tickets
|
|
|
|
|
|
|
|
|
|
|
|