diff --git a/fail2ban/server/actions.py b/fail2ban/server/actions.py index 01c2a761..962ecd2b 100644 --- a/fail2ban/server/actions.py +++ b/fail2ban/server/actions.py @@ -300,7 +300,7 @@ class Actions(JailThread, Mapping): def calcBanTime(self, banTime, banCount): return self._banExtra['evformula'](self.BanTimeIncr(banTime, banCount)) - def incrBanTime(self, bTicket, ip): + def incrBanTime(self, bTicket): """Check for IP address to increment ban time (if was already banned). Returns @@ -308,6 +308,7 @@ class Actions(JailThread, Mapping): float new ban time. """ + ip = bTicket.getIP() orgBanTime = self.__banManager.getBanTime() banTime = orgBanTime # check ip was already banned (increment time of ban): @@ -374,7 +375,7 @@ class Actions(JailThread, Mapping): try: # if ban time was not set: if not ticket.getRestored() and bTicket.getBanTime() is None: - btime = self.incrBanTime(bTicket, ip) + btime = self.incrBanTime(bTicket) bTicket.setBanTime(btime); except Exception as e: logSys.error('%s', e, exc_info=logSys.getEffectiveLevel()<=logging.DEBUG) diff --git a/fail2ban/server/database.py b/fail2ban/server/database.py index de575bcf..b75501cc 100644 --- a/fail2ban/server/database.py +++ b/fail2ban/server/database.py @@ -505,7 +505,9 @@ class Fail2BanDb(object): cur = self._db.cursor() return cur.execute(query, queryArgs) - def _getCurrentBans(self, jail = None, ip = None, forbantime=None): + def _getCurrentBans(self, jail = None, ip = None, forbantime=None, fromtime=None): + if fromtime is None: + fromtime = MyTime.time() #query = "SELECT count(ip), max(timeofban) FROM bans WHERE ip = ?" query = "SELECT ip, max(timeofban), bantime, bancount, data FROM bans WHERE 1" queryArgs = [] @@ -516,17 +518,17 @@ class Fail2BanDb(object): query += " AND ip=?" queryArgs.append(ip) query += " AND timeofban + bantime > ?" - queryArgs.append(MyTime.time()) + queryArgs.append(fromtime) if forbantime is not None: query += " AND timeofban > ?" - queryArgs.append(MyTime.time() - forbantime) + queryArgs.append(fromtime - forbantime) query += " GROUP BY ip ORDER BY ip, timeofban DESC" cur = self._db.cursor() #logSys.debug((query, queryArgs)); return cur.execute(query, queryArgs) - def getCurrentBans(self, jail = None, ip = None, forbantime=None): - if forbantime is None: + def getCurrentBans(self, jail = None, ip = None, forbantime=None, fromtime=None): + if forbantime is None and jail is not None: cacheKey = (ip, jail) if cacheKey in self._bansMergedCache: return self._bansMergedCache[cacheKey] @@ -534,7 +536,7 @@ class Fail2BanDb(object): tickets = [] ticket = None - results = list(self._getCurrentBans(jail=jail, ip=ip, forbantime=forbantime)) + results = list(self._getCurrentBans(jail=jail, ip=ip, forbantime=forbantime, fromtime=fromtime)) if results: matches = [] @@ -552,7 +554,7 @@ class Fail2BanDb(object): ticket.setAttempt(failures) tickets.append(ticket) - if forbantime is None: + if forbantime is None and jail is not None: self._bansMergedCache[cacheKey] = tickets if ip is None else ticket return tickets if ip is None else ticket diff --git a/fail2ban/tests/databasetestcase.py b/fail2ban/tests/databasetestcase.py index 84101c50..baea6eeb 100644 --- a/fail2ban/tests/databasetestcase.py +++ b/fail2ban/tests/databasetestcase.py @@ -278,3 +278,131 @@ class DatabaseTest(unittest.TestCase): self.db.purge() # Should leave jail as ban present self.assertEqual(len(self.db.getJailNames()), 1) self.assertEqual(len(self.db.getBans(jail=self.jail)), 1) + + +# Author: Serg G. Brester (sebres) +# + +__author__ = "Serg Brester" +__copyright__ = "Copyright (c) 2014 Serg G. Brester" + +class BanTimeIncr(unittest.TestCase): + + def setUp(self): + """Call before every test case.""" + if Fail2BanDb is None and sys.version_info >= (2,7): # pragma: no cover + raise unittest.SkipTest( + "Unable to import fail2ban database module as sqlite is not " + "available.") + elif Fail2BanDb is None: + return + _, self.dbFilename = tempfile.mkstemp(".db", "fail2ban_") + self.db = Fail2BanDb(self.dbFilename) + + def tearDown(self): + """Call after every test case.""" + if Fail2BanDb is None: # pragma: no cover + return + # Cleanup + os.remove(self.dbFilename) + + def testBanTimeIncr(self): + if Fail2BanDb is None: # pragma: no cover + return + jail = DummyJail() + jail.database = self.db + self.db.addJail(jail) + a = jail.actions + # we tests with initial ban time = 10 seconds: + a.setBanTime(10) + a.setBanTimeExtra('enabled', 'true') + a.setBanTimeExtra('multipliers', '1 2 4 8 16 32 64 128 256 512 1024 2048') + ip = "127.0.0.2" + # used as start and fromtime (like now but time independence, cause test case can run slow): + stime = int(MyTime.time()) + ticket = FailTicket(ip, stime, []) + # test ticket not yet found + self.assertEqual( + [a.incrBanTime(ticket) for i in xrange(3)], + [10, 10, 10] + ) + # add a ticket banned + self.db.addBan(jail, ticket) + # get a ticket already banned in this jail: + self.assertEqual( + [(banCount, timeOfBan, lastBanTime) for banCount, timeOfBan, lastBanTime in self.db.getBan(ip, jail, None, False)], + [(1, stime, 10)] + ) + # incr time and ban a ticket again : + ticket.setTime(stime + 15) + self.assertEqual(a.incrBanTime(ticket), 20) + self.db.addBan(jail, ticket) + # get a ticket already banned in this jail: + self.assertEqual( + [(banCount, timeOfBan, lastBanTime) for banCount, timeOfBan, lastBanTime in self.db.getBan(ip, jail, None, False)], + [(2, stime + 15, 20)] + ) + # get a ticket already banned in all jails: + self.assertEqual( + [(banCount, timeOfBan, lastBanTime) for banCount, timeOfBan, lastBanTime in self.db.getBan(ip, '', None, True)], + [(2, stime + 15, 20)] + ) + # search currently banned and 1 day later (nothing should be found): + self.assertEqual( + self.db.getCurrentBans(forbantime=-24*60*60, fromtime=stime), + [] + ) + # search currently banned anywhere: + restored_tickets = self.db.getCurrentBans(fromtime=stime) + self.assertEqual( + str(restored_tickets), + ('[FailTicket: ip=%s time=%s bantime=20 bancount=2 #attempts=0 matches=[]]' % (ip, stime + 15)) + ) + # search currently banned: + restored_tickets = self.db.getCurrentBans(jail=jail, fromtime=stime) + self.assertEqual( + str(restored_tickets), + ('[FailTicket: ip=%s time=%s bantime=20 bancount=2 #attempts=0 matches=[]]' % (ip, stime + 15)) + ) + restored_tickets[0].setRestored(True) + self.assertTrue(restored_tickets[0].getRestored()) + # increase ban multiple times: + for i in xrange(10): + ticket.setTime(stime + lastBanTime + 5) + banTime = a.incrBanTime(ticket) + self.assertEqual(banTime, lastBanTime * 2) + self.db.addBan(jail, ticket) + lastBanTime = banTime + # increase again, but the last multiplier reached (time not increased): + ticket.setTime(stime + lastBanTime + 5) + banTime = a.incrBanTime(ticket) + self.assertNotEqual(banTime, lastBanTime * 2) + self.assertEqual(banTime, lastBanTime) + self.db.addBan(jail, ticket) + lastBanTime = banTime + # add two tickets from yesterday: one unbanned (bantime already out-dated): + ticket2 = FailTicket(ip+'2', stime-24*60*60, []) + ticket2.setBanTime(12*60*60) + self.db.addBan(jail, ticket2) + # and one from yesterday also, but still currently banned : + ticket2 = FailTicket(ip+'1', stime-24*60*60, []) + ticket2.setBanTime(36*60*60) + self.db.addBan(jail, ticket2) + # search currently banned: + restored_tickets = self.db.getCurrentBans(fromtime=stime) + self.assertEqual(len(restored_tickets), 2) + self.assertEqual( + str(restored_tickets[0]), + 'FailTicket: ip=%s time=%s bantime=%s bancount=13 #attempts=0 matches=[]' % (ip, stime + lastBanTime + 5, lastBanTime) + ) + self.assertEqual( + str(restored_tickets[1]), + 'FailTicket: ip=%s time=%s bantime=%s bancount=1 #attempts=0 matches=[]' % (ip+'1', stime-24*60*60, 36*60*60) + ) + # search out-dated (give another fromtime now is -18 hours): + restored_tickets = self.db.getCurrentBans(fromtime=stime-18*60*60) + self.assertEqual(len(restored_tickets), 3) + self.assertEqual( + str(restored_tickets[2]), + 'FailTicket: ip=%s time=%s bantime=%s bancount=1 #attempts=0 matches=[]' % (ip+'2', stime-24*60*60, 12*60*60) + ) diff --git a/fail2ban/tests/utils.py b/fail2ban/tests/utils.py index bfd11074..8c45a1b6 100644 --- a/fail2ban/tests/utils.py +++ b/fail2ban/tests/utils.py @@ -184,6 +184,7 @@ def gatherTests(regexps=None, no_network=False): tests.addTest(unittest.makeSuite(misctestcase.CustomDateFormatsTest)) # Database tests.addTest(unittest.makeSuite(databasetestcase.DatabaseTest)) + tests.addTest(unittest.makeSuite(databasetestcase.BanTimeIncr)) # Filter tests.addTest(unittest.makeSuite(filtertestcase.IgnoreIP))