"Fossies" - the Fresh Open Source Software Archive

Member "fail2ban-0.11.1/fail2ban/tests/actionstestcase.py" (11 Jan 2020, 18725 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. See also the latest Fossies "Diffs" side-by-side code changes report for "actionstestcase.py": 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: Daniel Black
   21 # 
   22 
   23 __author__ = "Daniel Black"
   24 __copyright__ = "Copyright (c) 2013 Daniel Black"
   25 __license__ = "GPL"
   26 
   27 import time
   28 import os
   29 import tempfile
   30 
   31 from ..server.ticket import FailTicket
   32 from ..server.utils import Utils
   33 from .dummyjail import DummyJail
   34 from .utils import LogCaptureTestCase, with_alt_time, with_tmpdir, MyTime
   35 
   36 TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
   37 
   38 
   39 class ExecuteActions(LogCaptureTestCase):
   40 
   41     def setUp(self):
   42         """Call before every test case."""
   43         super(ExecuteActions, self).setUp()
   44         self.__jail = DummyJail()
   45         self.__actions = self.__jail.actions
   46 
   47     def tearDown(self):
   48         super(ExecuteActions, self).tearDown()
   49 
   50     def defaultAction(self, o={}):
   51         self.__actions.add('ip')
   52         act = self.__actions['ip']
   53         act.actionstart = 'echo ip start'+o.get('start', '')
   54         act.actionban = 'echo ip ban <ip>'+o.get('ban', '')
   55         act.actionunban = 'echo ip unban <ip>'+o.get('unban', '')
   56         act.actioncheck = 'echo ip check'+o.get('check', '')
   57         act.actionflush = 'echo ip flush'+o.get('flush', '')
   58         act.actionstop = 'echo ip stop'+o.get('stop', '')
   59         return act
   60 
   61     def testActionsAddDuplicateName(self):
   62         self.__actions.add('test')
   63         self.assertRaises(ValueError, self.__actions.add, 'test')
   64 
   65     def testActionsManipulation(self):
   66         self.__actions.add('test')
   67         self.assertTrue(self.__actions['test'])
   68         self.assertIn('test', self.__actions)
   69         self.assertNotIn('nonexistant action', self.__actions)
   70         self.__actions.add('test1')
   71         del self.__actions['test']
   72         del self.__actions['test1']
   73         self.assertNotIn('test', self.__actions)
   74         self.assertEqual(len(self.__actions), 0)
   75 
   76         self.__actions.setBanTime(127)
   77         self.assertEqual(self.__actions.getBanTime(),127)
   78         self.assertRaises(ValueError, self.__actions.removeBannedIP, '127.0.0.1')
   79 
   80     def testAddBannedIP(self):
   81         self.assertEqual(self.__actions.addBannedIP('192.0.2.1'), 1)
   82         self.assertLogged('Ban 192.0.2.1')
   83         self.pruneLog()
   84         self.assertEqual(self.__actions.addBannedIP(['192.0.2.1', '192.0.2.2', '192.0.2.3']), 2)
   85         self.assertLogged('192.0.2.1 already banned')
   86         self.assertNotLogged('Ban 192.0.2.1')
   87         self.assertLogged('Ban 192.0.2.2')
   88         self.assertLogged('Ban 192.0.2.3')
   89 
   90     def testActionsOutput(self):
   91         self.defaultAction()
   92         self.__actions.start()
   93         self.assertLogged("stdout: %r" % 'ip start', wait=True)
   94         self.__actions.stop()
   95         self.__actions.join()
   96         self.assertLogged("stdout: %r" % 'ip flush', "stdout: %r" % 'ip stop')
   97         self.assertEqual(self.__actions.status(),[("Currently banned", 0 ),
   98                ("Total banned", 0 ), ("Banned IP list", [] )])
   99 
  100     def testAddActionPython(self):
  101         self.__actions.add(
  102             "Action", os.path.join(TEST_FILES_DIR, "action.d/action.py"),
  103             {'opt1': 'value'})
  104 
  105         self.assertLogged("TestAction initialised")
  106 
  107         self.__actions.start()
  108         self.assertTrue( Utils.wait_for(lambda: self._is_logged("TestAction action start"), 3) )
  109 
  110         self.__actions.stop()
  111         self.__actions.join()
  112         self.assertLogged("TestAction action stop")
  113 
  114         self.assertRaises(IOError,
  115             self.__actions.add, "Action3", "/does/not/exist.py", {})
  116 
  117         # With optional argument
  118         self.__actions.add(
  119             "Action4", os.path.join(TEST_FILES_DIR, "action.d/action.py"),
  120             {'opt1': 'value', 'opt2': 'value2'})
  121         # With too many arguments
  122         self.assertRaises(
  123             TypeError, self.__actions.add, "Action5",
  124             os.path.join(TEST_FILES_DIR, "action.d/action.py"),
  125             {'opt1': 'value', 'opt2': 'value2', 'opt3': 'value3'})
  126         # Missing required argument
  127         self.assertRaises(
  128             TypeError, self.__actions.add, "Action5",
  129             os.path.join(TEST_FILES_DIR, "action.d/action.py"), {})
  130 
  131     def testAddPythonActionNOK(self):
  132         self.assertRaises(RuntimeError, self.__actions.add,
  133             "Action", os.path.join(TEST_FILES_DIR,
  134                 "action.d/action_noAction.py"),
  135             {})
  136         self.assertRaises(RuntimeError, self.__actions.add,
  137             "Action", os.path.join(TEST_FILES_DIR,
  138                 "action.d/action_nomethod.py"),
  139             {})
  140         self.__actions.add(
  141             "Action", os.path.join(TEST_FILES_DIR,
  142                 "action.d/action_errors.py"),
  143             {})
  144         self.__actions.start()
  145         self.assertTrue( Utils.wait_for(lambda: self._is_logged("Failed to start"), 3) )
  146         self.__actions.stop()
  147         self.__actions.join()
  148         self.assertLogged("Failed to stop")
  149 
  150     def testBanActionsAInfo(self):
  151         # Action which deletes IP address from aInfo
  152         self.__actions.add(
  153             "action1",
  154             os.path.join(TEST_FILES_DIR, "action.d/action_modifyainfo.py"),
  155             {})
  156         self.__actions.add(
  157             "action2",
  158             os.path.join(TEST_FILES_DIR, "action.d/action_modifyainfo.py"),
  159             {})
  160         self.__jail.putFailTicket(FailTicket("1.2.3.4"))
  161         self.__actions._Actions__checkBan()
  162         # Will fail if modification of aInfo from first action propagates
  163         # to second action, as both delete same key
  164         self.assertNotLogged("Failed to execute ban")
  165         self.assertLogged("action1 ban deleted aInfo IP")
  166         self.assertLogged("action2 ban deleted aInfo IP")
  167 
  168         self.__actions._Actions__flushBan()
  169         # Will fail if modification of aInfo from first action propagates
  170         # to second action, as both delete same key
  171         self.assertNotLogged("Failed to execute unban")
  172         self.assertLogged("action1 unban deleted aInfo IP")
  173         self.assertLogged("action2 unban deleted aInfo IP")
  174 
  175     @with_alt_time
  176     def testUnbanOnBusyBanBombing(self):
  177         # check unban happens in-between of "ban bombing" despite lower precedence,
  178         # if it is not work, we'll not see "Unbanned 30" (rather "Unbanned 50")
  179         # because then all the unbans occur earliest at flushing (after stop)
  180 
  181         # each 3rd ban we should see an unban check (and up to 5 tickets gets unbanned):
  182         self.__actions.banPrecedence = 3
  183         self.__actions.unbanMaxCount = 5
  184         self.__actions.setBanTime(100)
  185 
  186         self.__actions.start()
  187 
  188         MyTime.setTime(0); # avoid "expired bantime" (in 0.11)
  189         i = 0
  190         while i < 20:
  191             ip = "192.0.2.%d" % i
  192             self.__jail.putFailTicket(FailTicket(ip, 0))
  193             i += 1
  194 
  195         # wait for last ban (all 20 tickets gets banned):
  196         self.assertLogged(' / 20,', wait=True)
  197 
  198         MyTime.setTime(200); # unban time for 20 tickets reached
  199 
  200         while i < 50:
  201             ip = "192.0.2.%d" % i
  202             self.__jail.putFailTicket(FailTicket(ip, 200))
  203             i += 1
  204 
  205         # wait for last ban (all 50 tickets gets banned):
  206         self.assertLogged(' / 50,', wait=True)
  207         self.__actions.stop()
  208         self.__actions.join()
  209 
  210         self.assertLogged('Unbanned 30, 0 ticket(s)')
  211         self.assertNotLogged('Unbanned 50, 0 ticket(s)')
  212 
  213     def testActionsConsistencyCheck(self):
  214         act = self.defaultAction({'check':' <family>', 'flush':' <family>'})
  215         # flush for inet6 is intentionally "broken" here - test no unhandled except and invariant check:
  216         act['actionflush?family=inet6'] = act.actionflush + '; exit 1'
  217         act.actionstart_on_demand = True
  218         self.__actions.start()
  219         self.assertNotLogged("stdout: %r" % 'ip start')
  220 
  221         self.assertEqual(self.__actions.addBannedIP('192.0.2.1'), 1)
  222         self.assertEqual(self.__actions.addBannedIP('2001:db8::1'), 1)
  223         self.assertLogged('Ban 192.0.2.1', 'Ban 2001:db8::1',
  224             "stdout: %r" % 'ip start',
  225             "stdout: %r" % 'ip ban 192.0.2.1',
  226             "stdout: %r" % 'ip ban 2001:db8::1',
  227             all=True, wait=True)
  228 
  229         # check should fail (so cause stop/start):
  230         self.pruneLog('[test-phase 1a] simulate inconsistent irreparable env by unban')
  231         act['actioncheck?family=inet6'] = act.actioncheck + '; exit 1'
  232         self.__actions.removeBannedIP('2001:db8::1')
  233         self.assertLogged('Invariant check failed. Unban is impossible.',
  234             wait=True)
  235         self.pruneLog('[test-phase 1b] simulate inconsistent irreparable env by flush')
  236         self.__actions._Actions__flushBan()
  237         self.assertLogged(
  238             "stdout: %r" % 'ip flush inet4',
  239             "stdout: %r" % 'ip flush inet6',
  240             'Failed to flush bans',
  241             'No flush occurred, do consistency check',
  242             'Invariant check failed. Trying to restore a sane environment',
  243             "stdout: %r" % 'ip stop',  # same for both families
  244             'Failed to flush bans',
  245             all=True, wait=True)
  246 
  247         # check succeeds:
  248         self.pruneLog('[test-phase 2] consistent env')
  249         act['actioncheck?family=inet6'] = act.actioncheck
  250         self.assertEqual(self.__actions.addBannedIP('2001:db8::1'), 1)
  251         self.assertLogged('Ban 2001:db8::1',
  252             "stdout: %r" % 'ip start',   # same for both families
  253             "stdout: %r" % 'ip ban 2001:db8::1',
  254             all=True, wait=True)
  255         self.assertNotLogged("stdout: %r" % 'ip check inet4',
  256             all=True)
  257 
  258         self.pruneLog('[test-phase 3] failed flush in consistent env')
  259         self.__actions._Actions__flushBan()
  260         self.assertLogged('Failed to flush bans',
  261             'No flush occurred, do consistency check',
  262             "stdout: %r" % 'ip flush inet6',
  263             "stdout: %r" % 'ip check inet6',
  264             all=True, wait=True)
  265         self.assertNotLogged(
  266             "stdout: %r" % 'ip flush inet4',
  267             "stdout: %r" % 'ip stop',
  268             "stdout: %r" % 'ip start',
  269             'Unable to restore environment',
  270             all=True)
  271 
  272         # stop, flush succeeds:
  273         self.pruneLog('[test-phase end] flush successful')
  274         act['actionflush?family=inet6'] = act.actionflush
  275         self.__actions.stop()
  276         self.__actions.join()
  277         self.assertLogged(
  278             "stdout: %r" % 'ip flush inet6',
  279             "stdout: %r" % 'ip stop',    # same for both families
  280             'action ip terminated',
  281             all=True, wait=True)
  282         # no flush for inet4 (already successfully flushed):
  283         self.assertNotLogged("ERROR",
  284             "stdout: %r" % 'ip flush inet4',
  285             'Unban tickets each individualy',
  286             all=True)
  287 
  288     def testActionsConsistencyCheckDiffFam(self):
  289         # same as testActionsConsistencyCheck, but different start/stop commands for both families and repair on unban
  290         act = self.defaultAction({'start':' <family>', 'check':' <family>', 'flush':' <family>', 'stop':' <family>'})
  291         # flush for inet6 is intentionally "broken" here - test no unhandled except and invariant check:
  292         act['actionflush?family=inet6'] = act.actionflush + '; exit 1'
  293         act.actionstart_on_demand = True
  294         act.actionrepair_on_unban = True
  295         self.__actions.start()
  296         self.assertNotLogged("stdout: %r" % 'ip start')
  297 
  298         self.assertEqual(self.__actions.addBannedIP('192.0.2.1'), 1)
  299         self.assertEqual(self.__actions.addBannedIP('2001:db8::1'), 1)
  300         self.assertLogged('Ban 192.0.2.1', 'Ban 2001:db8::1',
  301             "stdout: %r" % 'ip start inet4',
  302             "stdout: %r" % 'ip ban 192.0.2.1',
  303             "stdout: %r" % 'ip start inet6',
  304             "stdout: %r" % 'ip ban 2001:db8::1',
  305             all=True, wait=True)
  306 
  307         # check should fail (so cause stop/start):
  308         act['actioncheck?family=inet6'] = act.actioncheck + '; exit 1'
  309         self.pruneLog('[test-phase 1a] simulate inconsistent irreparable env by unban')
  310         self.__actions.removeBannedIP('2001:db8::1')
  311         self.assertLogged('Invariant check failed. Trying to restore a sane environment',
  312             "stdout: %r" % 'ip stop inet6',
  313             all=True, wait=True)
  314         self.assertNotLogged(
  315             "stdout: %r" % 'ip start inet6', # start on demand (not on repair)
  316             "stdout: %r" % 'ip stop inet4',  # family inet4 is not affected
  317             "stdout: %r" % 'ip start inet4',
  318             all=True)
  319 
  320         self.pruneLog('[test-phase 1b] simulate inconsistent irreparable env by ban')
  321         self.assertEqual(self.__actions.addBannedIP('2001:db8::1'), 1)
  322         self.assertLogged('Invariant check failed. Trying to restore a sane environment',
  323             "stdout: %r" % 'ip stop inet6',
  324             "stdout: %r" % 'ip start inet6',
  325             "stdout: %r" % 'ip check inet6',
  326             'Unable to restore environment',
  327             'Failed to execute ban',
  328             all=True, wait=True)
  329         self.assertNotLogged(
  330             "stdout: %r" % 'ip stop inet4',  # family inet4 is not affected
  331             "stdout: %r" % 'ip start inet4',
  332             all=True)
  333 
  334         act['actioncheck?family=inet6'] = act.actioncheck
  335         self.assertEqual(self.__actions.addBannedIP('2001:db8::2'), 1)
  336         act['actioncheck?family=inet6'] = act.actioncheck + '; exit 1'
  337         self.pruneLog('[test-phase 1c] simulate inconsistent irreparable env by flush')
  338         self.__actions._Actions__flushBan()
  339         self.assertLogged(
  340             "stdout: %r" % 'ip flush inet4',
  341             "stdout: %r" % 'ip flush inet6',
  342             'Failed to flush bans',
  343             'No flush occurred, do consistency check',
  344             'Invariant check failed. Trying to restore a sane environment',
  345             "stdout: %r" % 'ip stop inet6',
  346             'Failed to flush bans in jail',
  347             all=True, wait=True)
  348         # start/stop should be called for inet6 only:
  349         self.assertNotLogged(
  350             "stdout: %r" % 'ip stop inet4',
  351             all=True)
  352 
  353         # check succeeds:
  354         self.pruneLog('[test-phase 2] consistent env')
  355         act['actioncheck?family=inet6'] = act.actioncheck
  356         self.assertEqual(self.__actions.addBannedIP('2001:db8::1'), 1)
  357         self.assertLogged('Ban 2001:db8::1',
  358             "stdout: %r" % 'ip start inet6',
  359             "stdout: %r" % 'ip ban 2001:db8::1',
  360             all=True, wait=True)
  361         self.assertNotLogged(
  362             "stdout: %r" % 'ip check inet4',
  363             "stdout: %r" % 'ip start inet4',
  364             all=True)
  365 
  366         self.pruneLog('[test-phase 3] failed flush in consistent env')
  367         act['actioncheck?family=inet6'] = act.actioncheck
  368         self.__actions._Actions__flushBan()
  369         self.assertLogged('Failed to flush bans',
  370             'No flush occurred, do consistency check',
  371             "stdout: %r" % 'ip flush inet6',
  372             "stdout: %r" % 'ip check inet6',
  373             all=True, wait=True)
  374         self.assertNotLogged(
  375             "stdout: %r" % 'ip flush inet4',
  376             "stdout: %r" % 'ip stop inet4',
  377             "stdout: %r" % 'ip start inet4',
  378             "stdout: %r" % 'ip stop inet6',
  379             "stdout: %r" % 'ip start inet6',
  380             all=True)
  381 
  382         # stop, flush succeeds:
  383         self.pruneLog('[test-phase end] flush successful')
  384         act['actionflush?family=inet6'] = act.actionflush
  385         self.__actions.stop()
  386         self.__actions.join()
  387         self.assertLogged(
  388             "stdout: %r" % 'ip flush inet6',
  389             "stdout: %r" % 'ip stop inet4',
  390             "stdout: %r" % 'ip stop inet6',
  391             'action ip terminated',
  392             all=True, wait=True)
  393         # no flush for inet4 (already successfully flushed):
  394         self.assertNotLogged("ERROR",
  395             "stdout: %r" % 'ip flush inet4',
  396             'Unban tickets each individualy',
  397             all=True)
  398 
  399     @with_alt_time
  400     @with_tmpdir
  401     def testActionsRebanBrokenAfterRepair(self, tmp):
  402         act = self.defaultAction({
  403             'start':' <family>; touch "<FN>"',
  404             'check':' <family>; test -f "<FN>"',
  405             'flush':' <family>; echo -n "" > "<FN>"',
  406             'stop': ' <family>; rm -f "<FN>"',
  407             'ban':  ' <family>; echo "<ip> <family>" >> "<FN>"',
  408         })
  409         act['FN'] = tmp+'/<family>'
  410         act.actionstart_on_demand = True
  411         act.actionrepair = 'echo ip repair <family>; touch "<FN>"'
  412         act.actionreban = 'echo ip reban <ip> <family>; echo "<ip> <family> -- rebanned" >> "<FN>"'
  413         self.pruneLog('[test-phase 0] initial ban')
  414         self.assertEqual(self.__actions.addBannedIP(['192.0.2.1', '2001:db8::1']), 2)
  415         self.assertLogged('Ban 192.0.2.1', 'Ban 2001:db8::1',
  416             "stdout: %r" % 'ip start inet4',
  417             "stdout: %r" % 'ip ban 192.0.2.1 inet4',
  418             "stdout: %r" % 'ip start inet6',
  419             "stdout: %r" % 'ip ban 2001:db8::1 inet6',
  420             all=True)
  421 
  422         self.pruneLog('[test-phase 1] check ban')
  423         self.dumpFile(tmp+'/inet4')
  424         self.assertLogged('192.0.2.1 inet4')
  425         self.assertNotLogged('2001:db8::1 inet6')
  426         self.pruneLog()
  427         self.dumpFile(tmp+'/inet6')
  428         self.assertLogged('2001:db8::1 inet6')
  429         self.assertNotLogged('192.0.2.1 inet4')
  430 
  431         # simulate 3 seconds past:
  432         MyTime.setTime(MyTime.time() + 4)
  433         # already banned produces events:
  434         self.pruneLog('[test-phase 2] check already banned')
  435         self.assertEqual(self.__actions.addBannedIP(['192.0.2.1', '2001:db8::1', '2001:db8::2']), 1)
  436         self.assertLogged(
  437             '192.0.2.1 already banned', '2001:db8::1 already banned', 'Ban 2001:db8::2',
  438             "stdout: %r" % 'ip check inet4', # both checks occurred
  439             "stdout: %r" % 'ip check inet6',
  440             all=True)
  441         self.dumpFile(tmp+'/inet4')
  442         self.dumpFile(tmp+'/inet6')
  443         # no reban should occur:
  444         self.assertNotLogged('Reban 192.0.2.1', 'Reban 2001:db8::1',
  445             "stdout: %r" % 'ip ban 192.0.2.1 inet4',
  446             "stdout: %r" % 'ip reban 192.0.2.1 inet4',
  447             "stdout: %r" % 'ip ban 2001:db8::1 inet6',
  448             "stdout: %r" % 'ip reban 2001:db8::1 inet6',
  449             '192.0.2.1 inet4 -- repaired',
  450             '2001:db8::1 inet6 -- repaired',
  451             all=True)
  452 
  453         # simulate 3 seconds past:
  454         MyTime.setTime(MyTime.time() + 4)
  455         # break env (remove both files, so check would fail):
  456         os.remove(tmp+'/inet4')
  457         os.remove(tmp+'/inet6')
  458         # test again already banned (it shall cause reban now):
  459         self.pruneLog('[test-phase 3a] check reban after sane env repaired')
  460         self.assertEqual(self.__actions.addBannedIP(['192.0.2.1', '2001:db8::1']), 2)
  461         self.assertLogged(
  462             "Invariant check failed. Trying to restore a sane environment",
  463             "stdout: %r" % 'ip repair inet4', # both repairs occurred
  464             "stdout: %r" % 'ip repair inet6',
  465             "Reban 192.0.2.1, action 'ip'", "Reban 2001:db8::1, action 'ip'", # both rebans also
  466             "stdout: %r" % 'ip reban 192.0.2.1 inet4',
  467             "stdout: %r" % 'ip reban 2001:db8::1 inet6',
  468             all=True)
  469 
  470         # now last IP (2001:db8::2) - no repair, but still old epoch of ticket, so it gets rebanned:
  471         self.pruneLog('[test-phase 3a] check reban by epoch mismatch (without repair)')
  472         self.assertEqual(self.__actions.addBannedIP('2001:db8::2'), 1)
  473         self.assertLogged(
  474             "Reban 2001:db8::2, action 'ip'",
  475             "stdout: %r" % 'ip reban 2001:db8::2 inet6',
  476             all=True)
  477         self.assertNotLogged(
  478             "Invariant check failed. Trying to restore a sane environment",
  479             "stdout: %r" % 'ip repair inet4', # both repairs occurred
  480             "stdout: %r" % 'ip repair inet6',
  481             "Reban 192.0.2.1, action 'ip'", "Reban 2001:db8::1, action 'ip'", # both rebans also
  482             "stdout: %r" % 'ip reban 192.0.2.1 inet4',
  483             "stdout: %r" % 'ip reban 2001:db8::1 inet6',
  484             all=True)
  485 
  486         # and bans present in files:
  487         self.pruneLog('[test-phase 4] check reban')
  488         self.dumpFile(tmp+'/inet4')
  489         self.assertLogged('192.0.2.1 inet4 -- rebanned')
  490         self.assertNotLogged('2001:db8::1 inet6 -- rebanned')
  491         self.pruneLog()
  492         self.dumpFile(tmp+'/inet6')
  493         self.assertLogged(
  494             '2001:db8::1 inet6 -- rebanned', 
  495             '2001:db8::2 inet6 -- rebanned', all=True)
  496         self.assertNotLogged('192.0.2.1 inet4 -- rebanned')
  497 
  498         # coverage - intended error in reban (no unhandled exception, message logged):
  499         act.actionreban = ''
  500         act.actionban = 'exit 1'
  501         self.assertEqual(self.__actions._Actions__reBan(FailTicket("192.0.2.1", 0)), 0)
  502         self.assertLogged(
  503             'Failed to execute reban',
  504             'Error banning 192.0.2.1', all=True)