mirror of https://github.com/fail2ban/fail2ban
small amend with several fixes and test coverage
parent
9ebf70cd6a
commit
a8c0cec4ac
|
@ -233,9 +233,11 @@ def substituteRecursiveTags(inptags, conditional='',
|
||||||
Dictionary of tags(keys) and their values, with tags
|
Dictionary of tags(keys) and their values, with tags
|
||||||
within the values recursively replaced.
|
within the values recursively replaced.
|
||||||
"""
|
"""
|
||||||
|
#logSys = getLogger("fail2ban")
|
||||||
|
tre_search = TAG_CRE.search
|
||||||
# copy return tags dict to prevent modifying of inptags:
|
# copy return tags dict to prevent modifying of inptags:
|
||||||
tags = inptags.copy()
|
tags = inptags.copy()
|
||||||
t = TAG_CRE
|
# init:
|
||||||
ignore = set(ignore)
|
ignore = set(ignore)
|
||||||
done = set()
|
done = set()
|
||||||
# repeat substitution while embedded-recursive (repFlag is True)
|
# repeat substitution while embedded-recursive (repFlag is True)
|
||||||
|
@ -247,48 +249,49 @@ def substituteRecursiveTags(inptags, conditional='',
|
||||||
if tag in ignore or tag in done: continue
|
if tag in ignore or tag in done: continue
|
||||||
value = orgval = str(tags[tag])
|
value = orgval = str(tags[tag])
|
||||||
# search and replace all tags within value, that can be interpolated using other tags:
|
# search and replace all tags within value, that can be interpolated using other tags:
|
||||||
m = t.search(value)
|
m = tre_search(value)
|
||||||
refCounts = {}
|
refCounts = {}
|
||||||
#logSys.log(5, 'TAG: %s, value: %s' % (tag, value))
|
#logSys.log(5, 'TAG: %s, value: %s' % (tag, value))
|
||||||
while m:
|
while m:
|
||||||
found_tag = m.group(1)
|
# found replacement tag:
|
||||||
|
rtag = m.group(1)
|
||||||
# don't replace tags that should be currently ignored (pre-replacement):
|
# don't replace tags that should be currently ignored (pre-replacement):
|
||||||
if found_tag in ignore:
|
if rtag in ignore:
|
||||||
m = t.search(value, m.end())
|
m = tre_search(value, m.end())
|
||||||
continue
|
continue
|
||||||
#logSys.log(5, 'found: %s' % found_tag)
|
#logSys.log(5, 'found: %s' % rtag)
|
||||||
if found_tag == tag or refCounts.get(found_tag, 1) > MAX_TAG_REPLACE_COUNT:
|
if rtag == tag or refCounts.get(rtag, 1) > MAX_TAG_REPLACE_COUNT:
|
||||||
# recursive definitions are bad
|
# recursive definitions are bad
|
||||||
#logSys.log(5, 'recursion fail tag: %s value: %s' % (tag, value) )
|
#logSys.log(5, 'recursion fail tag: %s value: %s' % (tag, value) )
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"properties contain self referencing definitions "
|
"properties contain self referencing definitions "
|
||||||
"and cannot be resolved, fail tag: %s, found: %s in %s, value: %s" %
|
"and cannot be resolved, fail tag: %s, found: %s in %s, value: %s" %
|
||||||
(tag, found_tag, refCounts, value))
|
(tag, rtag, refCounts, value))
|
||||||
repl = None
|
repl = None
|
||||||
if conditional:
|
if conditional:
|
||||||
repl = tags.get(found_tag + '?' + conditional)
|
repl = tags.get(rtag + '?' + conditional)
|
||||||
if repl is None:
|
if repl is None:
|
||||||
repl = tags.get(found_tag)
|
repl = tags.get(rtag)
|
||||||
# try to find tag using additional replacement (callable):
|
# try to find tag using additional replacement (callable):
|
||||||
if repl is None and addrepl is not None:
|
if repl is None and addrepl is not None:
|
||||||
repl = addrepl(found_tag)
|
repl = addrepl(rtag)
|
||||||
if repl is None:
|
if repl is None:
|
||||||
# Missing tags - just continue on searching after end of match
|
# Missing tags - just continue on searching after end of match
|
||||||
# Missing tags are ok - cInfo can contain aInfo elements like <HOST> and valid shell
|
# Missing tags are ok - cInfo can contain aInfo elements like <HOST> and valid shell
|
||||||
# constructs like <STDIN>.
|
# constructs like <STDIN>.
|
||||||
m = t.search(value, m.end())
|
m = tre_search(value, m.end())
|
||||||
continue
|
continue
|
||||||
value = value.replace('<%s>' % found_tag, repl)
|
value = value.replace('<%s>' % rtag, repl)
|
||||||
#logSys.log(5, 'value now: %s' % value)
|
#logSys.log(5, 'value now: %s' % value)
|
||||||
# increment reference count:
|
# increment reference count:
|
||||||
refCounts[found_tag] = refCounts.get(found_tag, 0) + 1
|
refCounts[rtag] = refCounts.get(rtag, 0) + 1
|
||||||
# the next match for replace:
|
# the next match for replace:
|
||||||
m = t.search(value, m.start())
|
m = tre_search(value, m.start())
|
||||||
#logSys.log(5, 'TAG: %s, newvalue: %s' % (tag, value))
|
#logSys.log(5, 'TAG: %s, newvalue: %s' % (tag, value))
|
||||||
# was substituted?
|
# was substituted?
|
||||||
if orgval != value:
|
if orgval != value:
|
||||||
# check still contains any tag - should be repeated (possible embedded-recursive substitution):
|
# check still contains any tag - should be repeated (possible embedded-recursive substitution):
|
||||||
if t.search(value):
|
if tre_search(value):
|
||||||
repFlag = True
|
repFlag = True
|
||||||
tags[tag] = value
|
tags[tag] = value
|
||||||
# no more sub tags (and no possible composite), add this tag to done set (just to be faster):
|
# no more sub tags (and no possible composite), add this tag to done set (just to be faster):
|
||||||
|
|
|
@ -252,6 +252,16 @@ class CommandAction(ActionBase):
|
||||||
# set:
|
# set:
|
||||||
self.__dict__[name] = value
|
self.__dict__[name] = value
|
||||||
|
|
||||||
|
def __delattr__(self, name):
|
||||||
|
if not name.startswith('_'):
|
||||||
|
# parameters changed - clear properties and substitution cache:
|
||||||
|
self.__properties = None
|
||||||
|
self.__substCache.clear()
|
||||||
|
#self._logSys.debug("Unset action %r %s", self._name, name)
|
||||||
|
self._logSys.debug(" Unset %s", name)
|
||||||
|
# del:
|
||||||
|
del self.__dict__[name]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _properties(self):
|
def _properties(self):
|
||||||
"""A dictionary of the actions properties.
|
"""A dictionary of the actions properties.
|
||||||
|
@ -404,15 +414,19 @@ class CommandAction(ActionBase):
|
||||||
# use cache if allowed:
|
# use cache if allowed:
|
||||||
if cache is not None:
|
if cache is not None:
|
||||||
ckey = (query, conditional)
|
ckey = (query, conditional)
|
||||||
value = cache.get(ckey)
|
try:
|
||||||
if value is not None:
|
return cache[ckey]
|
||||||
return value
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
# first try get cached tags dictionary:
|
# first try get cached tags dictionary:
|
||||||
subInfo = csubkey = None
|
subInfo = csubkey = None
|
||||||
if cache is not None:
|
if cache is not None:
|
||||||
csubkey = ('subst-tags', id(aInfo), conditional)
|
csubkey = ('subst-tags', id(aInfo), conditional)
|
||||||
subInfo = cache.get(csubkey)
|
try:
|
||||||
|
subInfo = cache[csubkey]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
# interpolation of dictionary:
|
# interpolation of dictionary:
|
||||||
if subInfo is None:
|
if subInfo is None:
|
||||||
subInfo = substituteRecursiveTags(aInfo, conditional, ignore=cls._escapedTags)
|
subInfo = substituteRecursiveTags(aInfo, conditional, ignore=cls._escapedTags)
|
||||||
|
@ -424,7 +438,7 @@ class CommandAction(ActionBase):
|
||||||
cache[csubkey] = subInfo
|
cache[csubkey] = subInfo
|
||||||
|
|
||||||
# substitution callable, used by interpolation of each tag
|
# substitution callable, used by interpolation of each tag
|
||||||
repeatSubst = {}
|
repeatSubst = {0: 0}
|
||||||
def substVal(m):
|
def substVal(m):
|
||||||
tag = m.group(1) # tagname from match
|
tag = m.group(1) # tagname from match
|
||||||
value = None
|
value = None
|
||||||
|
@ -441,19 +455,19 @@ class CommandAction(ActionBase):
|
||||||
value = cls.escapeTag(value)
|
value = cls.escapeTag(value)
|
||||||
# possible contains tags:
|
# possible contains tags:
|
||||||
if '<' in value:
|
if '<' in value:
|
||||||
repeatSubst[1] = True
|
repeatSubst[0] = 1
|
||||||
return value
|
return value
|
||||||
|
|
||||||
# interpolation of query:
|
# interpolation of query:
|
||||||
count = MAX_TAG_REPLACE_COUNT + 1
|
count = MAX_TAG_REPLACE_COUNT + 1
|
||||||
while True:
|
while True:
|
||||||
repeatSubst = {}
|
repeatSubst[0] = 0
|
||||||
value = TAG_CRE.sub(substVal, query)
|
value = TAG_CRE.sub(substVal, query)
|
||||||
# possible recursion ?
|
# possible recursion ?
|
||||||
if not repeatSubst or value == query: break
|
if not repeatSubst or value == query: break
|
||||||
query = value
|
query = value
|
||||||
count -= 1
|
count -= 1
|
||||||
if count <= 0: # pragma: no cover - almost impossible (because resolved above)
|
if count <= 0:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"unexpected too long replacement interpolation, "
|
"unexpected too long replacement interpolation, "
|
||||||
"possible self referencing definitions in query: %s" % (query,))
|
"possible self referencing definitions in query: %s" % (query,))
|
||||||
|
|
|
@ -40,12 +40,20 @@ class CommandActionTest(LogCaptureTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Call before every test case."""
|
"""Call before every test case."""
|
||||||
self.__action = CommandAction(None, "Test")
|
|
||||||
LogCaptureTestCase.setUp(self)
|
LogCaptureTestCase.setUp(self)
|
||||||
|
self.__action = CommandAction(None, "Test")
|
||||||
|
# prevent execute stop if start fails (or event not started at all):
|
||||||
|
self.__action_started = False
|
||||||
|
orgstart = self.__action.start
|
||||||
|
def _action_start():
|
||||||
|
self.__action_started = True
|
||||||
|
return orgstart()
|
||||||
|
self.__action.start = _action_start
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
"""Call after every test case."""
|
"""Call after every test case."""
|
||||||
self.__action.stop()
|
if self.__action_started:
|
||||||
|
self.__action.stop()
|
||||||
LogCaptureTestCase.tearDown(self)
|
LogCaptureTestCase.tearDown(self)
|
||||||
|
|
||||||
def testSubstituteRecursiveTags(self):
|
def testSubstituteRecursiveTags(self):
|
||||||
|
@ -196,6 +204,26 @@ class CommandActionTest(LogCaptureTestCase):
|
||||||
self.__action.replaceTag("abc",
|
self.__action.replaceTag("abc",
|
||||||
CallingMap(matches=lambda: int("a"))), "abc")
|
CallingMap(matches=lambda: int("a"))), "abc")
|
||||||
|
|
||||||
|
def testReplaceTagSelfRecursion(self):
|
||||||
|
setattr(self.__action, 'a', "<a")
|
||||||
|
setattr(self.__action, 'b', "c>")
|
||||||
|
setattr(self.__action, 'b?family=inet6', "b>")
|
||||||
|
setattr(self.__action, 'ac', "<a><b>")
|
||||||
|
setattr(self.__action, 'ab', "<ac>")
|
||||||
|
setattr(self.__action, 'x?family=inet6', "")
|
||||||
|
# produce self-referencing properties except:
|
||||||
|
self.assertRaisesRegexp(ValueError, r"properties contain self referencing definitions",
|
||||||
|
lambda: self.__action.replaceTag("<a><b>",
|
||||||
|
self.__action._properties, conditional="family=inet4")
|
||||||
|
)
|
||||||
|
# remore self-referencing in props:
|
||||||
|
delattr(self.__action, 'ac')
|
||||||
|
# produce self-referencing query except:
|
||||||
|
self.assertRaisesRegexp(ValueError, r"possible self referencing definitions in query",
|
||||||
|
lambda: self.__action.replaceTag("<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x<x>>>>>>>>>>>>>>>>>>>>>",
|
||||||
|
self.__action._properties, conditional="family=inet6")
|
||||||
|
)
|
||||||
|
|
||||||
def testReplaceTagConditionalCached(self):
|
def testReplaceTagConditionalCached(self):
|
||||||
setattr(self.__action, 'abc', "123")
|
setattr(self.__action, 'abc', "123")
|
||||||
setattr(self.__action, 'abc?family=inet4', "345")
|
setattr(self.__action, 'abc?family=inet4', "345")
|
||||||
|
|
Loading…
Reference in New Issue