diff --git a/config/jail.conf b/config/jail.conf index d3d49234..1ee0e08c 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -47,21 +47,38 @@ before = paths-debian.conf # "bantimeextra.enabled" allows to use database for searching of previously banned ip's to increase a # default ban time using special formula, default it is banTime * 1, 2, 4, 8, 16, 32... #bantimeextra.enabled = true + # "bantimeextra.findtime" is the max number of seconds that we search in the database, # if it is not specified - whole database will be used for ban searching # (please observe current "dbpurgeage" value of fail2ban.conf). #bantimeextra.findtime = 24*60*60 + # "bantimeextra.rndtime" is the max number of seconds using for mixing with random time # to prevent "clever" botnets calculate exact time IP can be unbanned again: #bantimeextra.rndtime = 5*60 + # "bantimeextra.maxtime" is the max number of seconds using the ban time can reach (don't grows further) #bantimeextra.maxtime = 24*60*60 + # "bantimeextra.factor" is a coefficient to calculate exponent growing of the formula, -# by default value of factor "2.0 / 2.885385" and default value of formula, the ban time +# for formula solution by default value of factor is "2.0 / 2.885385" and with default value of formula, the ban time # grows by 1, 2, 4, 8, 16 ... +# for multipliers solution by default value of factor is "1" and the ban time grows by specified multipliers +# corresponding ban count only #bantimeextra.factor = 2.0 / 2.885385 + # "bantimeextra.formula" used to calculate next value of ban time; #bantimeextra.formula = banTime * math.exp(float(banCount)*banFactor)/math.exp(1*banFactor) + +# "bantimeextra.multipliers" used to calculate next value of ban time instead of formula, coresponding +# previously ban count and given "bantimeextra.factor" (for multipliers default is 1); +# following example grows ban time by 1, 2, 4, 8, 16 ... and if last ban count greater as multipliers count, +# always used last multiplier (64 in example), for factor '1' and original ban time 600 - 10.6 hours +#bantimeextra.multipliers = 1 2 4 8 16 32 64 +# following example can be used for small initial ban time (bantime=60) - it grows more aggressive at begin, +# for bantime=60 the multipliers are minutes and equal: 1 min, 5 min, 30 min, 1 hour, 5 hour, 12 hour, 1 day, 2 day +#bantimeextra.multipliers = 1 5 30 60 300 720 1440 2880 + # "bantimeextra.overalljails" (if true) specifies the search of IP in the database will be executed # cross over all jails, if false (dafault), only current jail of the ban IP will be searched #bantimeextra.overalljails = false diff --git a/fail2ban/client/jailreader.py b/fail2ban/client/jailreader.py index 4d3f2801..06f70277 100644 --- a/fail2ban/client/jailreader.py +++ b/fail2ban/client/jailreader.py @@ -97,6 +97,7 @@ class JailReader(ConfigReader): ["string", "bantimeextra.findtime", None], ["string", "bantimeextra.factor", None], ["string", "bantimeextra.formula", None], + ["string", "bantimeextra.multipliers", None], ["string", "bantimeextra.maxtime", None], ["string", "bantimeextra.rndtime", None], ["bool", "bantimeextra.overalljails", None], diff --git a/fail2ban/server/actions.py b/fail2ban/server/actions.py index 5b1c738a..0013eb49 100644 --- a/fail2ban/server/actions.py +++ b/fail2ban/server/actions.py @@ -242,10 +242,18 @@ class Actions(JailThread, Mapping): logSys.debug(self._jail.name + ": action terminated") return True + class BanTimeIncr: + def __init__(self, banTime, banCount): + self.Time = banTime + self.Count = banCount + def setBanTimeExtra(self, opt, value): # merge previous extra with new option: be = self._banExtra; - be[opt] = value; + if value is not None: + be[opt] = value; + else: + del be[opt] logSys.info('Set banTimeExtra.%s = %s', opt, value) if opt == 'enabled': if isinstance(value, str): @@ -255,18 +263,31 @@ class Actions(JailThread, Mapping): if opt in ['findtime', 'maxtime', 'rndtime']: if not value is None: be[opt] = eval(value) - if opt == 'factor' or be.get('factor', None) is None: - be['factor'] = eval(be.get('factor', "2.0 / 2.885385")); - # prepare formula : - if opt in ['formula', 'maxtime', 'rndtime'] or be.get('evformula', None) is None: - be['formula'] = be.get('formula', 'banTime * math.exp(float(banCount)*banFactor)/math.exp(1*banFactor)') - evformula = be['formula']; - evformula = ('max(banTime, %s)' % evformula) + # prepare formula lambda: + if opt in ['formula', 'factor', 'maxtime', 'rndtime', 'multipliers'] or be.get('evformula', None) is None: + # split multifiers to an array begins with 0 (or empty if not set): + if opt == 'multipliers': + be['evmultipliers'] = [int(i) for i in (value.split(' ') if value is not None and value != '' else [])] + # if we have multifiers - use it in lambda, otherwise compile and use formula within lambda + multipliers = be.get('evmultipliers', []) + if len(multipliers): + banFactor = eval(be.get('factor', "1")) + evformula = lambda ban, banFactor=banFactor: ( + ban.Time * banFactor * multipliers[ban.Count if ban.Count < len(multipliers) else -1] + ) + else: + banFactor = eval(be.get('factor', "2.0 / 2.885385")) + formula = be.get('formula', 'ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)') + formula = compile(formula, '~inline-conf-expr~', 'eval') + evformula = lambda ban, banFactor=banFactor, formula=formula: max(ban.Time, eval(formula)) + # extend lambda with max time : if not be.get('maxtime', None) is None: - evformula = ('min(%s, %s)' % (evformula, be['maxtime'])) - # mix with random time (to prevent botnet calc exact time IP can be unbanned): + maxtime = be['maxtime'] + evformula = lambda ban, evformula=evformula: min(evformula(ban), maxtime) + # mix lambda with random time (to prevent bot-nets to calculate exact time IP can be unbanned): if not be.get('rndtime', None) is None: - evformula = ('(%s + random.random() * %s)' % (evformula, be['rndtime'])) + rndtime = be['rndtime'] + evformula = lambda ban, evformula=evformula: (evformula(ban) + random.random() * rndtime) # set to extra dict: be['evformula'] = evformula #logSys.info('banTimeExtra : %s' % json.dumps(be)) @@ -274,6 +295,9 @@ class Actions(JailThread, Mapping): def getBanTimeExtra(self, opt): return self._banExtra.get(opt, None) + def calcBanTime(self, banTime, banCount): + return self._banExtra['evformula'](self.BanTimeIncr(banTime, banCount)) + def incrBanTime(self, bTicket, ip): """Check for IP address to increment ban time (if was already banned). @@ -288,7 +312,6 @@ class Actions(JailThread, Mapping): try: be = self._banExtra; if banTime > 0 and be.get('enabled', False): - banFactor = be['factor']; # search IP in database and increase time if found: for banCount, timeOfBan, lastBanTime in \ self._jail.database.getBan(ip, self._jail, be.get('findtime', None), be.get('overalljails', False) \ @@ -296,7 +319,8 @@ class Actions(JailThread, Mapping): #logSys.debug('IP %s was already banned: %s #, %s' % (ip, banCount, timeOfBan)); bTicket.setBanCount(banCount); # calculate new ban time - banTime = eval(be['evformula']) + if banCount > 0: + banTime = be['evformula'](self.BanTimeIncr(banTime, banCount)) bTicket.setBanTime(banTime); logSys.info('[%s] %s was already banned: %s # at last %s - increase time %s to %s' % (self._jail.name, ip, banCount, datetime.datetime.fromtimestamp(timeOfBan).strftime("%Y-%m-%d %H:%M:%S"),