"Fossies" - the Fresh Open Source Software Archive

Member "fail2ban-0.11.1/fail2ban/server/jail.py" (11 Jan 2020, 11370 Bytes) of package /linux/misc/fail2ban-0.11.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "jail.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 0.10.5_vs_0.11.1.

    1 # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
    2 # vi: set ft=python sts=4 ts=4 sw=4 noet :
    3 
    4 # This file is part of Fail2Ban.
    5 #
    6 # Fail2Ban is free software; you can redistribute it and/or modify
    7 # it under the terms of the GNU General Public License as published by
    8 # the Free Software Foundation; either version 2 of the License, or
    9 # (at your option) any later version.
   10 #
   11 # Fail2Ban is distributed in the hope that it will be useful,
   12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
   13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14 # GNU General Public License for more details.
   15 #
   16 # You should have received a copy of the GNU General Public License
   17 # along with Fail2Ban; if not, write to the Free Software
   18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
   19 
   20 # Author: Cyril Jaquier
   21 
   22 __author__ = "Cyril Jaquier, Lee Clemens, Yaroslav Halchenko"
   23 __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2012 Lee Clemens, 2012 Yaroslav Halchenko"
   24 __license__ = "GPL"
   25 
   26 import logging
   27 import math
   28 import random
   29 import Queue
   30 
   31 from .actions import Actions
   32 from ..helpers import getLogger, _as_bool, extractOptions, MyTime
   33 from .mytime import MyTime
   34 
   35 # Gets the instance of the logger.
   36 logSys = getLogger(__name__)
   37 
   38 
   39 class Jail(object):
   40     """Fail2Ban jail, which manages a filter and associated actions.
   41 
   42     The class handles the initialisation of a filter, and actions. It's
   43     role is then to act as an interface between the filter and actions,
   44     passing bans detected by the filter, for the actions to then act upon.
   45 
   46     Parameters
   47     ----------
   48     name : str
   49         Name assigned to the jail.
   50     backend : str
   51         Backend to be used for filter. "auto" will attempt to pick
   52         the most preferred backend method. Default: "auto"
   53     db : Fail2BanDb
   54         Fail2Ban persistent database instance. Default: `None`
   55 
   56     Attributes
   57     ----------
   58     name
   59     database
   60     filter
   61     actions
   62     idle
   63     status
   64     """
   65 
   66     #Known backends. Each backend should have corresponding __initBackend method
   67     # yoh: stored in a list instead of a tuple since only
   68     #      list had .index until 2.6
   69     _BACKENDS = ['pyinotify', 'gamin', 'polling', 'systemd']
   70 
   71     def __init__(self, name, backend = "auto", db=None):
   72         self.__db = db
   73         # 26 based on iptable chain name limit of 30 less len('f2b-')
   74         if len(name) >= 26:
   75             logSys.warning("Jail name %r might be too long and some commands "
   76                             "might not function correctly. Please shorten"
   77                             % name)
   78         self.__name = name
   79         self.__queue = Queue.Queue()
   80         self.__filter = None
   81         # Extra parameters for increase ban time
   82         self._banExtra = {};
   83         logSys.info("Creating new jail '%s'" % self.name)
   84         if backend is not None:
   85             self._setBackend(backend)
   86         self.backend = backend
   87 
   88     def __repr__(self):
   89         return "%s(%r)" % (self.__class__.__name__, self.name)
   90 
   91     def _setBackend(self, backend):
   92         backend, beArgs = extractOptions(backend)
   93         backend = backend.lower()       # to assure consistent matching
   94 
   95         backends = self._BACKENDS
   96         if backend != 'auto':
   97             # we have got strict specification of the backend to use
   98             if not (backend in self._BACKENDS):
   99                 logSys.error("Unknown backend %s. Must be among %s or 'auto'"
  100                     % (backend, backends))
  101                 raise ValueError("Unknown backend %s. Must be among %s or 'auto'"
  102                     % (backend, backends))
  103             # so explore starting from it till the 'end'
  104             backends = backends[backends.index(backend):]
  105 
  106         for b in backends:
  107             initmethod = getattr(self, '_init%s' % b.capitalize())
  108             try:
  109                 initmethod(**beArgs)
  110                 if backend != 'auto' and b != backend:
  111                     logSys.warning("Could only initiated %r backend whenever "
  112                                    "%r was requested" % (b, backend))
  113                 else:
  114                     logSys.info("Initiated %r backend" % b)
  115                 self.__actions = Actions(self)
  116                 return                  # we are done
  117             except ImportError as e: # pragma: no cover
  118                 # Log debug if auto, but error if specific
  119                 logSys.log(
  120                     logging.DEBUG if backend == "auto" else logging.ERROR,
  121                     "Backend %r failed to initialize due to %s" % (b, e))
  122         # pragma: no cover
  123         # log error since runtime error message isn't printed, INVALID COMMAND
  124         logSys.error(
  125             "Failed to initialize any backend for Jail %r" % self.name)
  126         raise RuntimeError(
  127             "Failed to initialize any backend for Jail %r" % self.name)
  128 
  129     def _initPolling(self, **kwargs):
  130         from filterpoll import FilterPoll
  131         logSys.info("Jail '%s' uses poller %r" % (self.name, kwargs))
  132         self.__filter = FilterPoll(self, **kwargs)
  133 
  134     def _initGamin(self, **kwargs):
  135         # Try to import gamin
  136         from filtergamin import FilterGamin
  137         logSys.info("Jail '%s' uses Gamin %r" % (self.name, kwargs))
  138         self.__filter = FilterGamin(self, **kwargs)
  139 
  140     def _initPyinotify(self, **kwargs):
  141         # Try to import pyinotify
  142         from filterpyinotify import FilterPyinotify
  143         logSys.info("Jail '%s' uses pyinotify %r" % (self.name, kwargs))
  144         self.__filter = FilterPyinotify(self, **kwargs)
  145 
  146     def _initSystemd(self, **kwargs): # pragma: systemd no cover
  147         # Try to import systemd
  148         from filtersystemd import FilterSystemd
  149         logSys.info("Jail '%s' uses systemd %r" % (self.name, kwargs))
  150         self.__filter = FilterSystemd(self, **kwargs)
  151 
  152     @property
  153     def name(self):
  154         """Name of jail.
  155         """
  156         return self.__name
  157 
  158     @property
  159     def database(self):
  160         """The database used to store persistent data for the jail.
  161         """
  162         return self.__db
  163 
  164     @property
  165     def filter(self):
  166         """The filter which the jail is using to monitor log files.
  167         """
  168         return self.__filter
  169 
  170     @property
  171     def actions(self):
  172         """Actions object used to manage actions for jail.
  173         """
  174         return self.__actions
  175 
  176     @property
  177     def idle(self):
  178         """A boolean indicating whether jail is idle.
  179         """
  180         return self.filter.idle or self.actions.idle
  181 
  182     @idle.setter
  183     def idle(self, value):
  184         self.filter.idle = value
  185         self.actions.idle = value
  186 
  187     def status(self, flavor="basic"):
  188         """The status of the jail.
  189         """
  190         return [
  191             ("Filter", self.filter.status(flavor=flavor)),
  192             ("Actions", self.actions.status(flavor=flavor)),
  193             ]
  194 
  195     def putFailTicket(self, ticket):
  196         """Add a fail ticket to the jail.
  197 
  198         Used by filter to add a failure for banning.
  199         """
  200         self.__queue.put(ticket)
  201         # add ban to database moved to observer (should previously check not already banned 
  202         # and increase ticket time if "bantime.increment" set)
  203 
  204     def getFailTicket(self):
  205         """Get a fail ticket from the jail.
  206 
  207         Used by actions to get a failure for banning.
  208         """
  209         try:
  210             ticket = self.__queue.get(False)
  211             return ticket
  212         except Queue.Empty:
  213             return False
  214 
  215     def setBanTimeExtra(self, opt, value):
  216         # merge previous extra with new option:
  217         be = self._banExtra;
  218         if value == '':
  219             value = None
  220         if value is not None:
  221             be[opt] = value;
  222         elif opt in be:
  223             del be[opt]
  224         logSys.info('Set banTime.%s = %s', opt, value)
  225         if opt == 'increment':
  226             be[opt] = _as_bool(value)
  227             if be.get(opt) and self.database is None:
  228                 logSys.warning("ban time increment is not available as long jail database is not set")
  229         if opt in ['maxtime', 'rndtime']:
  230             if not value is None:
  231                 be[opt] = MyTime.str2seconds(value)
  232         # prepare formula lambda:
  233         if opt in ['formula', 'factor', 'maxtime', 'rndtime', 'multipliers'] or be.get('evformula', None) is None:
  234             # split multifiers to an array begins with 0 (or empty if not set):
  235             if opt == 'multipliers':
  236                 be['evmultipliers'] = [int(i) for i in (value.split(' ') if value is not None and value != '' else [])]
  237             # if we have multifiers - use it in lambda, otherwise compile and use formula within lambda
  238             multipliers = be.get('evmultipliers', [])
  239             banFactor = eval(be.get('factor', "1"))
  240             if len(multipliers):
  241                 evformula = lambda ban, banFactor=banFactor: (
  242                     ban.Time * banFactor * multipliers[ban.Count if ban.Count < len(multipliers) else -1]
  243                 )
  244             else:
  245                 formula = be.get('formula', 'ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor')
  246                 formula = compile(formula, '~inline-conf-expr~', 'eval')
  247                 evformula = lambda ban, banFactor=banFactor, formula=formula: max(ban.Time, eval(formula))
  248             # extend lambda with max time :
  249             if not be.get('maxtime', None) is None:
  250                 maxtime = be['maxtime']
  251                 evformula = lambda ban, evformula=evformula: min(evformula(ban), maxtime)
  252             # mix lambda with random time (to prevent bot-nets to calculate exact time IP can be unbanned):
  253             if not be.get('rndtime', None) is None:
  254                 rndtime = be['rndtime']
  255                 evformula = lambda ban, evformula=evformula: (evformula(ban) + random.random() * rndtime)
  256             # set to extra dict:
  257             be['evformula'] = evformula
  258         #logSys.info('banTimeExtra : %s' % json.dumps(be))
  259 
  260     def getBanTimeExtra(self, opt=None):
  261         if opt is not None:
  262             return self._banExtra.get(opt, None)
  263         return self._banExtra
  264 
  265     def getMaxBanTime(self):
  266         """Returns max possible ban-time of jail.
  267         """
  268         return self._banExtra.get("maxtime", -1) \
  269             if self._banExtra.get('increment') else self.actions.getBanTime()
  270 
  271     def restoreCurrentBans(self, correctBanTime=True):
  272         """Restore any previous valid bans from the database.
  273         """
  274         try:
  275             if self.database is not None:
  276                 if self._banExtra.get('increment'):
  277                     forbantime = None;
  278                     if correctBanTime:
  279                         correctBanTime = self.getMaxBanTime()
  280                 else:
  281                     # use ban time as search time if we have not enabled a increasing:
  282                     forbantime = self.actions.getBanTime()
  283                 for ticket in self.database.getCurrentBans(jail=self, forbantime=forbantime,
  284                     correctBanTime=correctBanTime, maxmatches=self.filter.failManager.maxMatches
  285                 ):
  286                     try:
  287                         #logSys.debug('restored ticket: %s', ticket)
  288                         if self.filter.inIgnoreIPList(ticket.getIP(), log_ignore=True): continue
  289                         # mark ticked was restored from database - does not put it again into db:
  290                         ticket.restored = True
  291                         # correct start time / ban time (by the same end of ban):
  292                         btm = ticket.getBanTime(forbantime)
  293                         diftm = MyTime.time() - ticket.getTime()
  294                         if btm != -1 and diftm > 0:
  295                             btm -= diftm
  296                         # ignore obsolete tickets:
  297                         if btm != -1 and btm <= 0:
  298                             continue
  299                         self.putFailTicket(ticket)
  300                     except Exception as e: # pragma: no cover
  301                         logSys.error('Restore ticket failed: %s', e, 
  302                             exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
  303         except Exception as e: # pragma: no cover
  304             logSys.error('Restore bans failed: %s', e,
  305                 exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
  306 
  307     def start(self):
  308         """Start the jail, by starting filter and actions threads.
  309 
  310         Once stated, also queries the persistent database to reinstate
  311         any valid bans.
  312         """
  313         logSys.debug("Starting jail %r", self.name)
  314         self.filter.start()
  315         self.actions.start()
  316         self.restoreCurrentBans()
  317         logSys.info("Jail %r started", self.name)
  318 
  319     def stop(self, stop=True, join=True):
  320         """Stop the jail, by stopping filter and actions threads.
  321         """
  322         if stop:
  323             logSys.debug("Stopping jail %r", self.name)
  324         for obj in (self.filter, self.actions):
  325             try:
  326                 ## signal to stop filter / actions:
  327                 if stop:
  328                     obj.stop()
  329                 ## wait for end of threads:
  330                 if join:
  331                     obj.join()
  332             except Exception as e:
  333                 logSys.error("Stop %r of jail %r failed: %s", obj, self.name, e,
  334                     exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
  335         if join:
  336             logSys.info("Jail %r stopped", self.name)
  337 
  338     def isAlive(self):
  339         """Check jail "isAlive" by checking filter and actions threads.
  340         """
  341         return self.filter.isAlive() or self.actions.isAlive()