"Fossies" - the Fresh Open Source Software Archive

Member "fail2ban-0.11.1/fail2ban/server/actions.py" (11 Jan 2020, 23173 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 "actions.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 
   23 __author__ = "Cyril Jaquier"
   24 __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
   25 __license__ = "GPL"
   26 
   27 import logging
   28 import os
   29 import sys
   30 import time
   31 from collections import Mapping
   32 try:
   33     from collections import OrderedDict
   34 except ImportError:
   35     OrderedDict = dict
   36 
   37 from .banmanager import BanManager, BanTicket
   38 from .ipdns import IPAddr
   39 from .jailthread import JailThread
   40 from .action import ActionBase, CommandAction, CallingMap
   41 from .mytime import MyTime
   42 from .observer import Observers
   43 from .utils import Utils
   44 from ..helpers import getLogger
   45 
   46 # Gets the instance of the logger.
   47 logSys = getLogger(__name__)
   48 
   49 
   50 class Actions(JailThread, Mapping):
   51     """Handles jail actions.
   52 
   53     This class handles the actions of the jail. Creation, deletion or to
   54     actions must be done through this class. This class is based on the
   55     Mapping type, and the `add` method must be used to add new actions.
   56     This class also starts and stops the actions, and fetches bans from
   57     the jail executing these bans via the actions.
   58 
   59     Parameters
   60     ----------
   61     jail: Jail
   62         The jail of which the actions belongs to.
   63 
   64     Attributes
   65     ----------
   66     daemon
   67     ident
   68     name
   69     status
   70     active : bool
   71         Control the state of the thread.
   72     idle : bool
   73         Control the idle state of the thread.
   74     sleeptime : int
   75         The time the thread sleeps for in the loop.
   76     """
   77 
   78     def __init__(self, jail):
   79         JailThread.__init__(self, name="f2b/a."+jail.name)
   80         ## The jail which contains this action.
   81         self._jail = jail
   82         self._actions = OrderedDict()
   83         ## The ban manager.
   84         self.__banManager = BanManager()
   85         self.banEpoch = 0
   86         self.__lastConsistencyCheckTM = 0
   87         ## Precedence of ban (over unban), so max number of tickets banned (to call an unban check):
   88         self.banPrecedence = 10
   89         ## Max count of outdated tickets to unban per each __checkUnBan operation:
   90         self.unbanMaxCount = self.banPrecedence * 2
   91 
   92     @staticmethod
   93     def _load_python_module(pythonModule):
   94         mod = Utils.load_python_module(pythonModule)
   95         if not hasattr(mod, "Action"): # pragma: no cover
   96             raise RuntimeError(
   97                 "%s module does not have 'Action' class" % pythonModule)
   98         elif not issubclass(mod.Action, ActionBase): # pragma: no cover
   99             raise RuntimeError(
  100                 "%s module %s does not implement required methods" % (
  101                     pythonModule, mod.Action.__name__))
  102         return mod
  103 
  104 
  105     def add(self, name, pythonModule=None, initOpts=None, reload=False):
  106         """Adds a new action.
  107 
  108         Add a new action if not already present, defaulting to standard
  109         `CommandAction`, or specified Python module.
  110 
  111         Parameters
  112         ----------
  113         name : str
  114             The name of the action.
  115         pythonModule : str, optional
  116             Path to Python file which must contain `Action` class.
  117             Default None, which means `CommandAction` is used.
  118         initOpts : dict, optional
  119             Options for Python Action, used as keyword arguments for
  120             initialisation. Default None.
  121 
  122         Raises
  123         ------
  124         ValueError
  125             If action name already exists.
  126         RuntimeError
  127             If external Python module does not have `Action` class
  128             or does not implement necessary methods as per `ActionBase`
  129             abstract class.
  130         """
  131         # Check is action name already exists
  132         if name in self._actions:
  133             if not reload:
  134                 raise ValueError("Action %s already exists" % name)
  135             # don't create new action if reload supported:
  136             action = self._actions[name]
  137             if hasattr(action, 'reload'):
  138                 # don't execute reload right now, reload after all parameters are actualized
  139                 if hasattr(action, 'clearAllParams'):
  140                     action.clearAllParams()
  141                     self._reload_actions[name] = initOpts
  142                 return
  143         ## Create new action:
  144         if pythonModule is None:
  145             action = CommandAction(self._jail, name)
  146         else:
  147             customActionModule = self._load_python_module(pythonModule)
  148             action = customActionModule.Action(self._jail, name, **initOpts)
  149         self._actions[name] = action
  150 
  151     def reload(self, begin=True):
  152         """ Begin or end of reloading resp. refreshing of all parameters
  153         """
  154         if begin:
  155             self._reload_actions = dict()
  156         else:
  157             if hasattr(self, '_reload_actions'):
  158                 # reload actions after all parameters set via stream:
  159                 for name, initOpts in self._reload_actions.iteritems():
  160                     if name in self._actions:
  161                         self._actions[name].reload(**(initOpts if initOpts else {}))
  162                 # remove obsolete actions (untouched by reload process):
  163                 delacts = OrderedDict((name, action) for name, action in self._actions.iteritems()
  164                     if name not in self._reload_actions)
  165                 if len(delacts):
  166                     # unban all tickets using removed actions only:
  167                     self.__flushBan(db=False, actions=delacts, stop=True)
  168                     # stop and remove it:
  169                     self.stopActions(actions=delacts)
  170                 delattr(self, '_reload_actions')
  171 
  172     def __getitem__(self, name):
  173         try:
  174             return self._actions[name]
  175         except KeyError:
  176             raise KeyError("Invalid Action name: %s" % name)
  177 
  178     def __delitem__(self, name):
  179         try:
  180             del self._actions[name]
  181         except KeyError:
  182             raise KeyError("Invalid Action name: %s" % name)
  183 
  184     def __iter__(self):
  185         return iter(self._actions)
  186 
  187     def __len__(self):
  188         return len(self._actions)
  189 
  190     def __eq__(self, other): # Required for Threading
  191         return False
  192 
  193     def __hash__(self): # Required for Threading
  194         return id(self)
  195 
  196     ##
  197     # Set the ban time.
  198     #
  199     # @param value the time
  200     
  201     def setBanTime(self, value):
  202         value = MyTime.str2seconds(value)
  203         self.__banManager.setBanTime(value)
  204         logSys.info("  banTime: %s" % value)
  205     
  206     ##
  207     # Get the ban time.
  208     #
  209     # @return the time
  210     
  211     def getBanTime(self):
  212         return self.__banManager.getBanTime()
  213 
  214     def getBanList(self, withTime=False):
  215         """Returns the list of banned IP addresses.
  216 
  217         Returns
  218         -------
  219         list
  220             The list of banned IP addresses.
  221         """
  222         return self.__banManager.getBanList(ordered=True, withTime=withTime)
  223 
  224     def addBannedIP(self, ip):
  225         """Ban an IP or list of IPs."""
  226         unixTime = MyTime.time()
  227 
  228         if isinstance(ip, list):
  229             # Multiple IPs:
  230             tickets = (BanTicket(ip, unixTime) for ip in ip)
  231         else:
  232             # Single IP:
  233             tickets = (BanTicket(ip, unixTime),)
  234 
  235         return self.__checkBan(tickets)
  236 
  237     def removeBannedIP(self, ip=None, db=True, ifexists=False):
  238         """Removes banned IP calling actions' unban method
  239 
  240         Remove a banned IP now, rather than waiting for it to expire,
  241         even if set to never expire.
  242 
  243         Parameters
  244         ----------
  245         ip : list, str, IPAddr or None
  246             The IP address (or multiple IPs as list) to unban or all IPs if None
  247 
  248         Raises
  249         ------
  250         ValueError
  251             If `ip` is not banned
  252         """
  253         # Unban all?
  254         if ip is None:
  255             return self.__flushBan(db)
  256         # Multiple IPs:
  257         if isinstance(ip, list):
  258             missed = []
  259             cnt = 0
  260             for i in ip:
  261                 try:
  262                     cnt += self.removeBannedIP(i, db, ifexists)
  263                 except ValueError:
  264                     if not ifexists:
  265                         missed.append(i)
  266             if missed:
  267                 raise ValueError("not banned: %r" % missed)
  268             return cnt
  269         # Single IP:
  270         # Always delete ip from database (also if currently not banned)
  271         if db and self._jail.database is not None:
  272             self._jail.database.delBan(self._jail, ip)
  273         # Find the ticket with the IP.
  274         ticket = self.__banManager.getTicketByID(ip)
  275         if ticket is not None:
  276             # Unban the IP.
  277             self.__unBan(ticket)
  278         else:
  279             msg = "%s is not banned" % ip
  280             logSys.log(logging.MSG, msg)
  281             if ifexists:
  282                 return 0
  283             raise ValueError(msg)
  284         return 1
  285 
  286 
  287     def stopActions(self, actions=None):
  288         """Stops the actions in reverse sequence (optionally filtered)
  289         """
  290         if actions is None:
  291             actions = self._actions
  292         revactions = actions.items()
  293         revactions.reverse()
  294         for name, action in revactions:
  295             try:
  296                 action.stop()
  297             except Exception as e:
  298                 logSys.error("Failed to stop jail '%s' action '%s': %s",
  299                     self._jail.name, name, e,
  300                     exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
  301             del self._actions[name]
  302             logSys.debug("%s: action %s terminated", self._jail.name, name)
  303 
  304 
  305     def run(self):
  306         """Main loop for Threading.
  307 
  308         This function is the main loop of the thread. It checks the jail
  309         queue and executes commands when an IP address is banned.
  310 
  311         Returns
  312         -------
  313         bool
  314             True when the thread exits nicely.
  315         """
  316         cnt = 0
  317         for name, action in self._actions.iteritems():
  318             try:
  319                 action.start()
  320             except Exception as e:
  321                 logSys.error("Failed to start jail '%s' action '%s': %s",
  322                     self._jail.name, name, e,
  323                     exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
  324         while self.active:
  325             if self.idle:
  326                 logSys.debug("Actions: enter idle mode")
  327                 Utils.wait_for(lambda: not self.active or not self.idle,
  328                     lambda: False, self.sleeptime)
  329                 logSys.debug("Actions: leave idle mode")
  330                 continue
  331             # wait for ban (stop if gets inactive):
  332             bancnt = Utils.wait_for(lambda: not self.active or self.__checkBan(), self.sleeptime)
  333             cnt += bancnt
  334             # unban if nothing is banned not later than banned tickets >= banPrecedence
  335             if not bancnt or cnt >= self.banPrecedence:
  336                 if self.active:
  337                     # let shrink the ban list faster
  338                     bancnt *= 2
  339                     self.__checkUnBan(bancnt if bancnt and bancnt < self.unbanMaxCount else self.unbanMaxCount)
  340                 cnt = 0
  341         
  342         self.__flushBan(stop=True)
  343         self.stopActions()
  344         return True
  345 
  346     class ActionInfo(CallingMap):
  347 
  348         CM_REPR_ITEMS = ("fid", "raw-ticket")
  349 
  350         AI_DICT = {
  351             "ip":               lambda self: self.__ticket.getIP(),
  352             "family":   lambda self: self['ip'].familyStr,
  353             "ip-rev":       lambda self: self['ip'].getPTR(''),
  354             "ip-host":  lambda self: self['ip'].getHost(),
  355             "fid":          lambda self: self.__ticket.getID(),
  356             "failures": lambda self: self.__ticket.getAttempt(),
  357             "time":         lambda self: self.__ticket.getTime(),
  358             "bantime":  lambda self: self._getBanTime(),
  359             "bancount":  lambda self: self.__ticket.getBanCount(),
  360             "matches":  lambda self: "\n".join(self.__ticket.getMatches()),
  361             # to bypass actions, that should not be executed for restored tickets
  362             "restored": lambda self: (1 if self.__ticket.restored else 0),
  363             # extra-interpolation - all match-tags (captured from the filter):
  364             "F-*":          lambda self, tag=None: self.__ticket.getData(tag),
  365             # merged info:
  366             "ipmatches":            lambda self: "\n".join(self._mi4ip(True).getMatches()),
  367             "ipjailmatches":    lambda self: "\n".join(self._mi4ip().getMatches()),
  368             "ipfailures":           lambda self: self._mi4ip(True).getAttempt(),
  369             "ipjailfailures":   lambda self: self._mi4ip().getAttempt(),
  370             # raw ticket info:
  371             "raw-ticket":           lambda self: repr(self.__ticket)
  372         }
  373 
  374         __slots__ = CallingMap.__slots__ + ('__ticket', '__jail', '__mi4ip')
  375 
  376         def __init__(self, ticket, jail=None, immutable=True, data=AI_DICT):
  377             self.__ticket = ticket
  378             self.__jail = jail
  379             self.storage = dict()
  380             self.immutable = immutable
  381             self.data = data
  382         
  383         def copy(self): # pragma: no cover
  384             return self.__class__(self.__ticket, self.__jail, self.immutable, self.data.copy())
  385 
  386         def _getBanTime(self):
  387             btime = self.__ticket.getBanTime()
  388             if btime is None: btime = self.__jail.actions.getBanTime()
  389             return int(btime)
  390 
  391         def _mi4ip(self, overalljails=False):
  392             """Gets bans merged once, a helper for lambda(s), prevents stop of executing action by any exception inside.
  393 
  394             This function never returns None for ainfo lambdas - always a ticket (merged or single one)
  395             and prevents any errors through merging (to guarantee ban actions will be executed).
  396             [TODO] move merging to observer - here we could wait for merge and read already merged info from a database
  397 
  398             Parameters
  399             ----------
  400             overalljails : bool
  401                 switch to get a merged bans :
  402                 False - (default) bans merged for current jail only
  403                 True - bans merged for all jails of current ip address
  404 
  405             Returns
  406             -------
  407             BanTicket 
  408                 merged or self ticket only
  409             """
  410             if not hasattr(self, '__mi4ip'):
  411                 self.__mi4ip = {}
  412             mi = self.__mi4ip
  413             idx = 'all' if overalljails else 'jail'
  414             if idx in mi:
  415                 return mi[idx] if mi[idx] is not None else self.__ticket
  416             try:
  417                 jail = self.__jail
  418                 ip = self['ip']
  419                 mi[idx] = None
  420                 if not jail.database: # pragma: no cover
  421                     return self.__ticket
  422                 if overalljails:
  423                     mi[idx] = jail.database.getBansMerged(ip=ip)
  424                 else:
  425                     mi[idx] = jail.database.getBansMerged(ip=ip, jail=jail)
  426             except Exception as e:
  427                 logSys.error(
  428                     "Failed to get %s bans merged, jail '%s': %s",
  429                     idx, jail.name, e,
  430                     exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
  431             return mi[idx] if mi[idx] is not None else self.__ticket
  432 
  433 
  434     def __getActionInfo(self, ticket):
  435         aInfo = Actions.ActionInfo(ticket, self._jail)
  436         return aInfo
  437 
  438     def __getFailTickets(self, count=100):
  439         """Generator to get maximal count failure tickets from fail-manager."""
  440         cnt = 0
  441         while cnt < count:
  442             ticket = self._jail.getFailTicket()
  443             if not ticket:
  444                 break
  445             yield ticket
  446             cnt += 1
  447 
  448     def __checkBan(self, tickets=None):
  449         """Check for IP address to ban.
  450 
  451         If tickets are not specified look in the jail queue for FailTicket. If a ticket is available,
  452         it executes the "ban" command and adds a ticket to the BanManager.
  453 
  454         Returns
  455         -------
  456         bool
  457             True if an IP address get banned.
  458         """
  459         cnt = 0
  460         if not tickets:
  461             tickets = self.__getFailTickets(self.banPrecedence)
  462         rebanacts = None
  463         for ticket in tickets:
  464 
  465             bTicket = BanTicket.wrap(ticket)
  466             btime = ticket.getBanTime(self.__banManager.getBanTime())
  467             ip = bTicket.getIP()
  468             aInfo = self.__getActionInfo(bTicket)
  469             reason = {}
  470             if self.__banManager.addBanTicket(bTicket, reason=reason):
  471                 cnt += 1
  472                 # report ticket to observer, to check time should be increased and hereafter observer writes ban to database (asynchronous)
  473                 if Observers.Main is not None and not bTicket.restored:
  474                     Observers.Main.add('banFound', bTicket, self._jail, btime)
  475                 logSys.notice("[%s] %sBan %s", self._jail.name, ('' if not bTicket.restored else 'Restore '), ip)
  476                 # do actions :
  477                 for name, action in self._actions.iteritems():
  478                     try:
  479                         if ticket.restored and getattr(action, 'norestored', False):
  480                             continue
  481                         if not aInfo.immutable: aInfo.reset()
  482                         action.ban(aInfo)
  483                     except Exception as e:
  484                         logSys.error(
  485                             "Failed to execute ban jail '%s' action '%s' "
  486                             "info '%r': %s",
  487                             self._jail.name, name, aInfo, e,
  488                             exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
  489                 # after all actions are processed set banned flag:
  490                 bTicket.banned = True
  491                 if self.banEpoch: # be sure tickets always have the same ban epoch (default 0):
  492                     bTicket.banEpoch = self.banEpoch
  493             else:
  494                 if reason.get('expired', 0):
  495                     logSys.info('[%s] Ignore %s, expired bantime', self._jail.name, ip)
  496                     continue
  497                 bTicket = reason.get('ticket', bTicket)
  498                 # if already banned (otherwise still process some action)
  499                 if bTicket.banned:
  500                     # compare time of failure occurrence with time ticket was really banned:
  501                     diftm = ticket.getTime() - bTicket.getTime()
  502                     # log already banned with following level:
  503                     #   DEBUG   - before 3 seconds - certain interval for it, because of possible latency by recognizing in backends, etc.
  504                     #   NOTICE  - before 60 seconds - may still occur if action is slow, or very high load in backend,
  505                     #   WARNING - after 60 seconds - very long time, something may be wrong
  506                     ll = logging.DEBUG   if diftm < 3 \
  507                     else logging.NOTICE  if diftm < 60 \
  508                     else logging.WARNING
  509                     logSys.log(ll, "[%s] %s already banned", self._jail.name, ip)
  510                     # if long time after ban - do consistency check (something is wrong here):
  511                     if bTicket.banEpoch == self.banEpoch and diftm > 3:
  512                         # avoid too often checks:
  513                         if not rebanacts and MyTime.time() > self.__lastConsistencyCheckTM + 3:
  514                             for action in self._actions.itervalues():
  515                                 action.consistencyCheck()
  516                             self.__lastConsistencyCheckTM = MyTime.time()
  517                     # check epoch in order to reban it:
  518                     if bTicket.banEpoch < self.banEpoch:
  519                         if not rebanacts: rebanacts = dict(
  520                             (name, action) for name, action in self._actions.iteritems()
  521                                 if action.banEpoch > bTicket.banEpoch)
  522                         cnt += self.__reBan(bTicket, actions=rebanacts)
  523                 else: # pragma: no cover - unexpected: ticket is not banned for some reasons - reban using all actions:
  524                     cnt += self.__reBan(bTicket)
  525         if cnt:
  526             logSys.debug("Banned %s / %s, %s ticket(s) in %r", cnt, 
  527                 self.__banManager.getBanTotal(), self.__banManager.size(), self._jail.name)
  528         return cnt
  529 
  530     def __reBan(self, ticket, actions=None, log=True):
  531         """Repeat bans for the ticket.
  532 
  533         Executes the actions in order to reban the host given in the
  534         ticket.
  535 
  536         Parameters
  537         ----------
  538         ticket : Ticket
  539             Ticket to reban
  540         """
  541         actions = actions or self._actions
  542         ip = ticket.getIP()
  543         aInfo = self.__getActionInfo(ticket)
  544         if log:
  545             logSys.notice("[%s] Reban %s%s", self._jail.name, aInfo["ip"], (', action %r' % actions.keys()[0] if len(actions) == 1 else ''))
  546         for name, action in actions.iteritems():
  547             try:
  548                 logSys.debug("[%s] action %r: reban %s", self._jail.name, name, ip)
  549                 if not aInfo.immutable: aInfo.reset()
  550                 action.reban(aInfo)
  551             except Exception as e:
  552                 logSys.error(
  553                     "Failed to execute reban jail '%s' action '%s' "
  554                     "info '%r': %s",
  555                     self._jail.name, name, aInfo, e,
  556                     exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
  557                 return 0
  558         # after all actions are processed set banned flag:
  559         ticket.banned = True
  560         if self.banEpoch: # be sure tickets always have the same ban epoch (default 0):
  561             ticket.banEpoch = self.banEpoch
  562         return 1
  563 
  564     def _prolongBan(self, ticket):
  565         # prevent to prolong ticket that was removed in-between,
  566         # if it in ban list - ban time already prolonged (and it stays there):
  567         if not self.__banManager._inBanList(ticket): return
  568         # do actions :
  569         aInfo = None
  570         for name, action in self._actions.iteritems():
  571             try:
  572                 if ticket.restored and getattr(action, 'norestored', False):
  573                     continue
  574                 if not action._prolongable:
  575                     continue
  576                 if aInfo is None:
  577                     aInfo = self.__getActionInfo(ticket)
  578                 if not aInfo.immutable: aInfo.reset()
  579                 action.prolong(aInfo)
  580             except Exception as e:
  581                 logSys.error(
  582                     "Failed to execute ban jail '%s' action '%s' "
  583                     "info '%r': %s",
  584                     self._jail.name, name, aInfo, e,
  585                     exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
  586 
  587     def __checkUnBan(self, maxCount=None):
  588         """Check for IP address to unban.
  589 
  590         Unban IP addresses which are outdated.
  591         """
  592         lst = self.__banManager.unBanList(MyTime.time(), maxCount)
  593         for ticket in lst:
  594             self.__unBan(ticket)
  595         cnt = len(lst)
  596         if cnt:
  597             logSys.debug("Unbanned %s, %s ticket(s) in %r", 
  598                 cnt, self.__banManager.size(), self._jail.name)
  599         return cnt
  600 
  601     def __flushBan(self, db=False, actions=None, stop=False):
  602         """Flush the ban list.
  603 
  604         Unban all IP address which are still in the banning list.
  605 
  606         If actions specified, don't flush list - just execute unban for 
  607         given actions (reload, obsolete resp. removed actions).
  608         """
  609         log = True
  610         if actions is None:
  611             logSys.debug("  Flush ban list")
  612             lst = self.__banManager.flushBanList()
  613         else:
  614             log = False # don't log "[jail] Unban ..." if removing actions only.
  615             lst = iter(self.__banManager)
  616         cnt = 0
  617         # first we'll execute flush for actions supporting this operation:
  618         unbactions = {}
  619         for name, action in (actions if actions is not None else self._actions).iteritems():
  620             try:
  621                 if hasattr(action, 'flush') and (not isinstance(action, CommandAction) or action.actionflush):
  622                     logSys.notice("[%s] Flush ticket(s) with %s", self._jail.name, name)
  623                     if action.flush():
  624                         continue
  625             except Exception as e:
  626                 logSys.error("Failed to flush bans in jail '%s' action '%s': %s",
  627                     self._jail.name, name, e,
  628                     exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
  629                 logSys.info("No flush occurred, do consistency check")
  630                 if hasattr(action, 'consistencyCheck'):
  631                     def _beforeRepair():
  632                         if stop and not getattr(action, 'actionrepair_on_unban', None): # don't need repair on stop
  633                             self._logSys.error("Invariant check failed. Flush is impossible.")
  634                             return False
  635                         return True
  636                     action.consistencyCheck(_beforeRepair)
  637                     continue
  638             # fallback to single unbans:
  639             logSys.debug("  Unban tickets each individualy")
  640             unbactions[name] = action
  641         actions = unbactions
  642         # flush the database also:
  643         if db and self._jail.database is not None:
  644             logSys.debug("  Flush jail in database")
  645             self._jail.database.delBan(self._jail)
  646         # unban each ticket with non-flusheable actions:
  647         for ticket in lst:
  648             # unban ip:
  649             self.__unBan(ticket, actions=actions, log=log)
  650             cnt += 1
  651         logSys.debug("  Unbanned %s, %s ticket(s) in %r", 
  652             cnt, self.__banManager.size(), self._jail.name)
  653         return cnt
  654 
  655     def __unBan(self, ticket, actions=None, log=True):
  656         """Unbans host corresponding to the ticket.
  657 
  658         Executes the actions in order to unban the host given in the
  659         ticket.
  660 
  661         Parameters
  662         ----------
  663         ticket : FailTicket
  664             Ticket of failures of which to unban
  665         """
  666         if actions is None:
  667             unbactions = self._actions
  668         else:
  669             unbactions = actions
  670         ip = ticket.getIP()
  671         aInfo = self.__getActionInfo(ticket)
  672         if log:
  673             logSys.notice("[%s] Unban %s", self._jail.name, aInfo["ip"])
  674         for name, action in unbactions.iteritems():
  675             try:
  676                 logSys.debug("[%s] action %r: unban %s", self._jail.name, name, ip)
  677                 if not aInfo.immutable: aInfo.reset()
  678                 action.unban(aInfo)
  679             except Exception as e:
  680                 logSys.error(
  681                     "Failed to execute unban jail '%s' action '%s' "
  682                     "info '%r': %s",
  683                     self._jail.name, name, aInfo, e,
  684                     exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
  685 
  686     def status(self, flavor="basic"):
  687         """Status of current and total ban counts and current banned IP list.
  688         """
  689         # TODO: Allow this list to be printed as 'status' output
  690         supported_flavors = ["basic", "cymru"]
  691         if flavor is None or flavor not in supported_flavors:
  692             logSys.warning("Unsupported extended jail status flavor %r. Supported: %s" % (flavor, supported_flavors))
  693         # Always print this information (basic)
  694         ret = [("Currently banned", self.__banManager.size()),
  695                ("Total banned", self.__banManager.getBanTotal()),
  696                ("Banned IP list", self.__banManager.getBanList())]
  697         if flavor == "cymru":
  698             cymru_info = self.__banManager.getBanListExtendedCymruInfo()
  699             ret += \
  700                 [("Banned ASN list", self.__banManager.geBanListExtendedASN(cymru_info)),
  701                  ("Banned Country list", self.__banManager.geBanListExtendedCountry(cymru_info)),
  702                  ("Banned RIR list", self.__banManager.geBanListExtendedRIR(cymru_info))]
  703         return ret