From 33e9e2174ad98466c6f33dd78ba7baf30f3a8340 Mon Sep 17 00:00:00 2001 From: sebres Date: Tue, 20 Jan 2015 17:09:13 +0100 Subject: [PATCH] recursive/embedded version of issue/907; test cases merged from remote-tracking branch 'yarikoptic:enh/embedded_tags' into issue/907 infinite busy loop on _escapedTags match in substituteRecursiveTags gh-907 --- ChangeLog | 3 ++ fail2ban/server/action.py | 61 ++++++++++++++++++-------------- fail2ban/tests/actiontestcase.py | 5 +++ 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/ChangeLog b/ChangeLog index c24c9dc8..f01ed930 100644 --- a/ChangeLog +++ b/ChangeLog @@ -30,6 +30,9 @@ ver. 0.9.2 (2014/XX/XXX) - wanna-be-released - New Features: - New filter: - postfix-rbl Thanks Lee Clemens + - New recursive embedded substitution feature added: + - `<HOST>` becomes `` for PREF=`IPV4`; + - `<HOST>` becomes `1.2.3.4` for PREF=`IPV4` and IPV4HOST=`1.2.3.4`; - New interpolation feature for config readers - `%(known/parameter)s`. (means last known option with name `parameter`). This interpolation makes possible to extend a stock filter or jail regexp in .local file diff --git a/fail2ban/server/action.py b/fail2ban/server/action.py index c69ba88f..bfd2adc2 100644 --- a/fail2ban/server/action.py +++ b/fail2ban/server/action.py @@ -378,34 +378,41 @@ class CommandAction(ActionBase): Dictionary of tags(keys) and their values, with tags within the values recursively replaced. """ - t = re.compile(r'<([^ >]+)>') - for tag in tags.iterkeys(): - if tag in cls._escapedTags: - # Escaped so won't match - continue - value = str(tags[tag]) - m = t.search(value) - done = [] - #logSys.log(5, 'TAG: %s, value: %s' % (tag, value)) - while m: - found_tag = m.group(1) - #logSys.log(5, 'found: %s' % found_tag) - if found_tag == tag or found_tag in done: - # recursive definitions are bad - #logSys.log(5, 'recursion fail tag: %s value: %s' % (tag, value) ) - return False - if found_tag in cls._escapedTags or not tags.has_key(found_tag): - # Escaped or missing tags - just continue on searching after end of match - # Missing tags are ok - cInfo can contain aInfo elements like and valid shell - # constructs like . - m = t.search(value, m.end()) + t = re.compile(r'<([^ <>]+)>') + while True: + repFlag = False + for tag in tags.iterkeys(): + if tag in cls._escapedTags: + # Escaped so won't match continue - value = value.replace('<%s>' % found_tag , tags[found_tag]) - #logSys.log(5, 'value now: %s' % value) - done.append(found_tag) - m = t.search(value, m.start()) - #logSys.log(5, 'TAG: %s, newvalue: %s' % (tag, value)) - tags[tag] = value + value = str(tags[tag]) + m = t.search(value) + done = [] + #logSys.log(5, 'TAG: %s, value: %s' % (tag, value)) + while m: + found_tag = m.group(1) + #logSys.log(5, 'found: %s' % found_tag) + if found_tag == tag or found_tag in done: + # recursive definitions are bad + #logSys.log(5, 'recursion fail tag: %s value: %s' % (tag, value) ) + return False + if found_tag in cls._escapedTags or not tags.has_key(found_tag): + # Escaped or missing tags - just continue on searching after end of match + # Missing tags are ok - cInfo can contain aInfo elements like and valid shell + # constructs like . + m = t.search(value, m.end()) + continue + value = value.replace('<%s>' % found_tag , tags[found_tag]) + #logSys.log(5, 'value now: %s' % value) + done.append(found_tag) + m = t.search(value, m.start()) + #logSys.log(5, 'TAG: %s, newvalue: %s' % (tag, value)) + # if was substituted, check again later: + if tags[tag] != value: + repFlag = True + tags[tag] = value + if not repFlag: + break return tags @staticmethod diff --git a/fail2ban/tests/actiontestcase.py b/fail2ban/tests/actiontestcase.py index 36e0ddb8..7cd0cd4d 100644 --- a/fail2ban/tests/actiontestcase.py +++ b/fail2ban/tests/actiontestcase.py @@ -73,6 +73,11 @@ class CommandActionTest(LogCaptureTestCase): 'ABC': '123 192.0.2.0', 'xyz': '890 123 192.0.2.0', }) + # obscure embedded case + self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<HOST>', 'PREF': 'IPV4'}), + {'A': '', 'PREF': 'IPV4'}) + self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<HOST>', 'PREF': 'IPV4', 'IPV4HOST': '1.2.3.4'}), + {'A': '1.2.3.4', 'PREF': 'IPV4', 'IPV4HOST': '1.2.3.4'}) def testReplaceTag(self): aInfo = {