introduced new flag "banned" as property, used to recognize the ticket was really banned;

get/set restored flag functions rewritten to property "restored" similar to "banned";
several code optimizations and tests extensions;
pull/1557/head
sebres 2016-09-09 16:12:48 +02:00
parent 2108216d33
commit f6197200a9
6 changed files with 96 additions and 30 deletions

View File

@ -359,9 +359,10 @@ class Actions(JailThread, Mapping):
aInfo["ipjailmatches"] = lambda: "\n".join(mi4ip().getMatches()) aInfo["ipjailmatches"] = lambda: "\n".join(mi4ip().getMatches())
aInfo["ipfailures"] = lambda: mi4ip(True).getAttempt() aInfo["ipfailures"] = lambda: mi4ip(True).getAttempt()
aInfo["ipjailfailures"] = lambda: mi4ip().getAttempt() aInfo["ipjailfailures"] = lambda: mi4ip().getAttempt()
if self.__banManager.addBanTicket(bTicket): reason = {}
if self.__banManager.addBanTicket(bTicket, reason=reason):
cnt += 1 cnt += 1
logSys.notice("[%s] %sBan %s", self._jail.name, ('' if not bTicket.getRestored() else 'Restore '), ip) logSys.notice("[%s] %sBan %s", self._jail.name, ('' if not bTicket.restored else 'Restore '), ip)
for name, action in self._actions.iteritems(): for name, action in self._actions.iteritems():
try: try:
action.ban(aInfo.copy()) action.ban(aInfo.copy())
@ -371,8 +372,22 @@ class Actions(JailThread, Mapping):
"info '%r': %s", "info '%r': %s",
self._jail.name, name, aInfo, e, self._jail.name, name, aInfo, e,
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG) exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
# after all actions are processed set banned flag:
bTicket.banned = True
else: else:
logSys.notice("[%s] %s already banned", self._jail.name, ip) bTicket = reason['ticket']
# if already banned (otherwise still process some action)
if bTicket.banned:
# compare time of failure occurrence with time ticket was really banned:
diftm = ticket.getTime() - bTicket.getTime()
# log already banned with following level:
# DEBUG - before 3 seconds - certain interval for it, because of possible latency by recognizing in backends, etc.
# NOTICE - before 60 seconds - may still occurre if action are slow, or very high load in backend,
# WARNING - after 60 seconds - very long time, something may be wrong
ll = logging.DEBUG if diftm < 3 \
else logging.NOTICE if diftm < 60 \
else logging.WARNING
logSys.log(ll, "[%s] %s already banned", self._jail.name, ip)
if cnt: if cnt:
logSys.debug("Banned %s / %s, %s ticket(s) in %r", cnt, logSys.debug("Banned %s / %s, %s ticket(s) in %r", cnt,
self.__banManager.getBanTotal(), self.__banManager.size(), self._jail.name) self.__banManager.getBanTotal(), self.__banManager.size(), self._jail.name)

View File

@ -256,29 +256,30 @@ class BanManager:
# @param ticket the ticket # @param ticket the ticket
# @return True if the IP address is not in the ban list # @return True if the IP address is not in the ban list
def addBanTicket(self, ticket): def addBanTicket(self, ticket, reason={}):
eob = ticket.getEndOfBanTime(self.__banTime)
with self.__lock: with self.__lock:
# check already banned # check already banned
fid = ticket.getID() fid = ticket.getID()
oldticket = self.__banList.get(fid) oldticket = self.__banList.get(fid)
if oldticket: if oldticket:
# if already permanent reason['ticket'] = oldticket
btold, told = oldticket.getBanTime(self.__banTime), oldticket.getTime() # if new time for end of ban is larger than already banned end-time:
if btold == -1: if eob > oldticket.getEndOfBanTime(self.__banTime):
return False
# if given time is less than already banned time
btnew, tnew = ticket.getBanTime(self.__banTime), ticket.getTime()
if btnew != -1 and tnew + btnew <= told + btold:
return False
# we have longest ban - set new (increment) ban time # we have longest ban - set new (increment) ban time
oldticket.setTime(tnew) reason['prolong'] = 1
oldticket.setBanTime(btnew) btm = ticket.getBanTime(self.__banTime)
# if not permanent:
if btm != -1:
diftm = ticket.getTime() - oldticket.getTime()
if diftm > 0:
btm += diftm
oldticket.setBanTime(btm)
return False return False
# not yet banned - add new # not yet banned - add new one:
self.__banList[fid] = ticket self.__banList[fid] = ticket
self.__banTotal += 1 self.__banTotal += 1
# correct next unban time: # correct next unban time:
eob = ticket.getEndOfBanTime(self.__banTime)
if self.__nextUnbanTime > eob: if self.__nextUnbanTime > eob:
self.__nextUnbanTime = eob self.__nextUnbanTime = eob
return True return True

View File

@ -28,7 +28,7 @@ import Queue
from .actions import Actions from .actions import Actions
from ..client.jailreader import JailReader from ..client.jailreader import JailReader
from ..helpers import getLogger from ..helpers import getLogger, MyTime
# Gets the instance of the logger. # Gets the instance of the logger.
logSys = getLogger(__name__) logSys = getLogger(__name__)
@ -194,7 +194,7 @@ class Jail(object):
Used by filter to add a failure for banning. Used by filter to add a failure for banning.
""" """
self.__queue.put(ticket) self.__queue.put(ticket)
if not ticket.getRestored() and self.database is not None: if not ticket.restored and self.database is not None:
self.database.addBan(self, ticket) self.database.addBan(self, ticket)
def getFailTicket(self): def getFailTicket(self):
@ -203,7 +203,8 @@ class Jail(object):
Used by actions to get a failure for banning. Used by actions to get a failure for banning.
""" """
try: try:
return self.__queue.get(False) ticket = self.__queue.get(False)
return ticket
except Queue.Empty: except Queue.Empty:
return False return False
@ -217,7 +218,17 @@ class Jail(object):
#logSys.debug('restored ticket: %s', ticket) #logSys.debug('restored ticket: %s', ticket)
if not self.filter.inIgnoreIPList(ticket.getIP(), log_ignore=True): if not self.filter.inIgnoreIPList(ticket.getIP(), log_ignore=True):
# mark ticked was restored from database - does not put it again into db: # mark ticked was restored from database - does not put it again into db:
ticket.setRestored(True) ticket.restored = True
# correct start time / ban time (by the same end of ban):
btm = ticket.getBanTime(forbantime)
diftm = MyTime.time() - ticket.getTime()
if btm != -1 and diftm > 0:
btm -= diftm
# ignore obsolete tickets:
if btm != -1 and btm <= 0:
continue
ticket.setTime(MyTime.time())
ticket.setBanTime(btm)
self.putFailTicket(ticket) self.putFailTicket(ticket)
except Exception as e: # pragma: no cover except Exception as e: # pragma: no cover
logSys.error('%s', e, exc_info=logSys.getEffectiveLevel()<=logging.DEBUG) logSys.error('%s', e, exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)

View File

@ -34,9 +34,10 @@ from .mytime import MyTime
logSys = getLogger(__name__) logSys = getLogger(__name__)
class Ticket: class Ticket(object):
RESTORED = 0x01 RESTORED = 0x01
BANNED = 0x08
def __init__(self, ip=None, time=None, matches=None, data={}, ticket=None): def __init__(self, ip=None, time=None, matches=None, data={}, ticket=None):
"""Ticket constructor """Ticket constructor
@ -135,14 +136,25 @@ class Ticket:
def getMatches(self): def getMatches(self):
return self._data.get('matches', []) return self._data.get('matches', [])
def setRestored(self, value): @property
def restored(self):
return self._flags & Ticket.RESTORED
@restored.setter
def restored(self, value):
if value: if value:
self._flags = Ticket.RESTORED self._flags |= Ticket.RESTORED
else: else:
self._flags &= ~(Ticket.RESTORED) self._flags &= ~(Ticket.RESTORED)
def getRestored(self): @property
return self._flags & Ticket.RESTORED def banned(self):
return self._flags & Ticket.BANNED
@banned.setter
def banned(self, value):
if value:
self._flags |= Ticket.BANNED
else:
self._flags &= ~(Ticket.BANNED)
def setData(self, *args, **argv): def setData(self, *args, **argv):
# if overwrite - set data and filter None values: # if overwrite - set data and filter None values:

View File

@ -53,11 +53,15 @@ class AddFailure(unittest.TestCase):
self.assertEqual(self.__banManager.size(), 1) self.assertEqual(self.__banManager.size(), 1)
def testAddDuplicateWithTime(self): def testAddDuplicateWithTime(self):
defBanTime = self.__banManager.getBanTime()
prevEndOfBanTime = 0
# add again a duplicate : # add again a duplicate :
# 1) with newer start time and the same ban time # 0) with same start time and the same (default) ban time
# 1) with newer start time and the same (default) ban time
# 2) with same start time and longer ban time # 2) with same start time and longer ban time
# 3) with permanent ban time (-1) # 3) with permanent ban time (-1)
for tnew, btnew in ( for tnew, btnew in (
(1167605999.0, None),
(1167605999.0 + 100, None), (1167605999.0 + 100, None),
(1167605999.0, 24*60*60), (1167605999.0, 24*60*60),
(1167605999.0, -1), (1167605999.0, -1),
@ -71,9 +75,14 @@ class AddFailure(unittest.TestCase):
self.assertEqual(self.__banManager.size(), 1) self.assertEqual(self.__banManager.size(), 1)
# pop ticket and check it was prolonged : # pop ticket and check it was prolonged :
banticket = self.__banManager.getTicketByID(ticket2.getID()) banticket = self.__banManager.getTicketByID(ticket2.getID())
self.assertEqual(banticket.getTime(), ticket2.getTime()) self.assertEqual(banticket.getEndOfBanTime(defBanTime), ticket2.getEndOfBanTime(defBanTime))
self.assertEqual(banticket.getTime(), ticket2.getTime()) self.assertTrue(banticket.getEndOfBanTime(defBanTime) > prevEndOfBanTime)
self.assertEqual(banticket.getBanTime(), ticket2.getBanTime(self.__banManager.getBanTime())) prevEndOfBanTime = ticket1.getEndOfBanTime(defBanTime)
# but the start time should not be changed (+ 100 is ignored):
self.assertEqual(banticket.getTime(), 1167605999.0)
# if prolong to permanent, it should also have permanent ban time:
if btnew == -1:
self.assertEqual(banticket.getBanTime(defBanTime), -1)
def testInListOK(self): def testInListOK(self):
self.assertTrue(self.__banManager.addBanTicket(self.__ticket)) self.assertTrue(self.__banManager.addBanTicket(self.__ticket))

View File

@ -108,6 +108,24 @@ class TicketTests(unittest.TestCase):
self.assertEqual(ft2.getLastTime(), ft.getLastTime()) self.assertEqual(ft2.getLastTime(), ft.getLastTime())
self.assertEqual(ft2.getBanTime(), ft.getBanTime()) self.assertEqual(ft2.getBanTime(), ft.getBanTime())
def testTicketFlags(self):
flags = ('restored', 'banned')
ticket = Ticket('test', 0)
trueflags = []
for v in (True, False, True):
for f in flags:
setattr(ticket, f, v)
if v:
trueflags.append(f)
else:
trueflags.remove(f)
for f2 in flags:
self.assertEqual(bool(getattr(ticket, f2)), f2 in trueflags)
## inherite props from another tockets:
ticket = FailTicket(ticket=ticket)
for f2 in flags:
self.assertTrue(bool(getattr(ticket, f2)))
def testTicketData(self): def testTicketData(self):
t = BanTicket('193.168.0.128', None, ['first', 'second']) t = BanTicket('193.168.0.128', None, ['first', 'second'])
# expand data (no overwrites, matches are available) : # expand data (no overwrites, matches are available) :