mirror of https://github.com/fail2ban/fail2ban
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
parent
2108216d33
commit
f6197200a9
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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) :
|
||||||
|
|
Loading…
Reference in New Issue