diff --git a/fail2ban/server/action.py b/fail2ban/server/action.py index 0942a3e5..94114862 100644 --- a/fail2ban/server/action.py +++ b/fail2ban/server/action.py @@ -341,6 +341,8 @@ class CommandAction(ActionBase): # set: self.__dict__[name] = value + __setitem__ = __setattr__ + def __delattr__(self, name): if not name.startswith('_'): # parameters changed - clear properties and substitution cache: @@ -373,13 +375,12 @@ class CommandAction(ActionBase): return self.__substCache def _getOperation(self, tag, family): - # be sure family is enclosed as conditional value (if not overwritten in action): - if family and self._hasCondSection: - if 'family' not in self._properties and 'family?family='+family not in self._properties: - self._properties['family?family='+family] = family - # replace operation tag (interpolate all values): + # replace operation tag (interpolate all values), be sure family is enclosed as conditional value + # (as lambda in addrepl so only if not overwritten in action): return self.replaceTag(tag, self._properties, - conditional=('family='+family if family else ''), cache=self.__substCache) + conditional=('family='+family if family else ''), + addrepl=(lambda tag:family if tag == 'family' else None), + cache=self.__substCache) def _executeOperation(self, tag, operation, family=[]): """Executes the operation commands (like "actionstart", "actionstop", etc). @@ -564,7 +565,7 @@ class CommandAction(ActionBase): return value @classmethod - def replaceTag(cls, query, aInfo, conditional='', cache=None): + def replaceTag(cls, query, aInfo, conditional='', addrepl=None, cache=None): """Replaces tags in `query` with property values. Parameters @@ -605,7 +606,8 @@ class CommandAction(ActionBase): pass # interpolation of dictionary: if subInfo is None: - subInfo = substituteRecursiveTags(aInfo, conditional, ignore=cls._escapedTags) + subInfo = substituteRecursiveTags(aInfo, conditional, ignore=cls._escapedTags, + addrepl=addrepl) # cache if possible: if csubkey is not None: cache[csubkey] = subInfo diff --git a/fail2ban/tests/actionstestcase.py b/fail2ban/tests/actionstestcase.py index 86b539f0..9d6b16c6 100644 --- a/fail2ban/tests/actionstestcase.py +++ b/fail2ban/tests/actionstestcase.py @@ -213,9 +213,9 @@ class ExecuteActions(LogCaptureTestCase): @with_alt_time def testActionsConsistencyCheck(self): - # flush is broken - test no unhandled except and invariant check: + # flush for inet6 is intentionally "broken" here - test no unhandled except and invariant check: act = self.defaultAction() - setattr(act, 'actionflush?family=inet6', 'echo ip flush ; exit 1') + act['actionflush?family=inet6'] = 'echo ip flush ; exit 1' act.actionstart_on_demand = True self.__actions.start() self.assertNotLogged("stdout: %r" % 'ip start') @@ -228,12 +228,28 @@ class ExecuteActions(LogCaptureTestCase): "stdout: %r" % 'ip ban 2001:db8::1', all=True, wait=True) + # check should fail (so cause stop/start): + self.pruneLog('[test-phase 1] simulate inconsistent env') + act['actioncheck?family=inet6'] = 'echo ip check ; exit 1' + self.__actions._Actions__flushBan() + self.assertLogged('Failed to flush bans', + 'No flush occured, do consistency check', + 'Invariant check failed. Trying to restore a sane environment', + "stdout: %r" % 'ip stop', + "stdout: %r" % 'ip start', + all=True, wait=True) + + # check succeeds: + self.pruneLog('[test-phase 2] consistent env') + act['actioncheck?family=inet6'] = act.actioncheck self.__actions._Actions__flushBan() self.assertLogged('Failed to flush bans', 'No flush occured, do consistency check', "stdout: %r" % 'ip ban 192.0.2.1', all=True, wait=True) + act['actionflush?family=inet6'] = act.actionflush + self.__actions.stop() self.__actions.join()