diff --git a/.travis.yml b/.travis.yml index 398c120a..502af5be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,8 @@ install: # coverage - travis_retry pip install coverage # coveralls (note coveralls doesn't support 2.6 now): - - if [[ $TRAVIS_PYTHON_VERSION != 2.6* ]]; then F2B_COV=1; else F2B_COV=0; fi + #- if [[ $TRAVIS_PYTHON_VERSION != 2.6* ]]; then F2B_COV=1; else F2B_COV=0; fi + - F2B_COV=1 - if [[ "$F2B_COV" = 1 ]]; then travis_retry pip install coveralls; fi # codecov: - travis_retry pip install codecov diff --git a/ChangeLog b/ChangeLog index a022f17c..d294ad54 100644 --- a/ChangeLog +++ b/ChangeLog @@ -10,6 +10,8 @@ ver. 1.0.1-dev-1 (20??/??/??) - development nightly edition ----------- ### Compatibility: +* the minimum supported python version is now 2.7, if you have previous python version + you can use the 0.11 version of fail2ban or upgrade python (or even build it from source). * potential incompatibility by parsing of options of `backend`, `filter` and `action` parameters (if they are partially incorrect), because fail2ban could throw an error now (doesn't silently bypass it anymore). * to v.0.11: diff --git a/README.md b/README.md index ba5c149e..6bf94c25 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Installation: this case, you should use that instead.** Required: -- [Python2 >= 2.6 or Python >= 3.2](https://www.python.org) or [PyPy](https://pypy.org) +- [Python2 >= 2.7 or Python >= 3.2](https://www.python.org) or [PyPy](https://pypy.org) - python-setuptools, python-distutils or python3-setuptools for installation from source Optional: diff --git a/fail2ban/server/actions.py b/fail2ban/server/actions.py index 54e0c02a..0cd2ec33 100644 --- a/fail2ban/server/actions.py +++ b/fail2ban/server/actions.py @@ -32,10 +32,7 @@ try: from collections.abc import Mapping except ImportError: from collections import Mapping -try: - from collections import OrderedDict -except ImportError: - OrderedDict = dict +from collections import OrderedDict from .banmanager import BanManager, BanTicket from .ipdns import IPAddr diff --git a/fail2ban/server/server.py b/fail2ban/server/server.py index 3853bbc4..36ed1b0d 100644 --- a/fail2ban/server/server.py +++ b/fail2ban/server/server.py @@ -728,9 +728,7 @@ class Server: except (ValueError, KeyError): # pragma: no cover # Is known to be thrown after logging was shutdown once # with older Pythons -- seems to be safe to ignore there - # At least it was still failing on 2.6.2-0ubuntu1 (jaunty) - if (2, 6, 3) <= sys.version_info < (3,) or \ - (3, 2) <= sys.version_info: + if sys.version_info < (3,) or sys.version_info >= (3, 2): raise # detailed format by deep log levels (as DEBUG=10): if logger.getEffectiveLevel() <= logging.DEBUG: # pragma: no cover diff --git a/fail2ban/server/utils.py b/fail2ban/server/utils.py index 8483b013..18073ea7 100644 --- a/fail2ban/server/utils.py +++ b/fail2ban/server/utils.py @@ -30,11 +30,7 @@ import sys from threading import Lock import time from ..helpers import getLogger, _merge_dicts, uni_decode - -try: - from collections import OrderedDict -except ImportError: # pragma: 3.x no cover - OrderedDict = dict +from collections import OrderedDict if sys.version_info >= (3, 3): import importlib.machinery @@ -100,24 +96,12 @@ class Utils(): with self.__lock: # clean cache if max count reached: if len(cache) >= self.maxCount: - if OrderedDict is not dict: - # ordered (so remove some from ahead, FIFO) - while cache: - (ck, cv) = cache.popitem(last=False) - # if not yet expired (but has free slot for new entry): - if cv[1] > t and len(cache) < self.maxCount: - break - else: # pragma: 3.x no cover (dict is in 2.6 only) - remlst = [] - for (ck, cv) in cache.iteritems(): - # if expired: - if cv[1] <= t: - remlst.append(ck) - for ck in remlst: - self._cache.pop(ck, None) - # if still max count - remove any one: - while cache and len(cache) >= self.maxCount: - cache.popitem() + # ordered (so remove some from ahead, FIFO) + while cache: + (ck, cv) = cache.popitem(last=False) + # if not yet expired (but has free slot for new entry): + if cv[1] > t and len(cache) < self.maxCount: + break # set now: cache[k] = (v, t + self.maxTime) diff --git a/fail2ban/tests/actiontestcase.py b/fail2ban/tests/actiontestcase.py index 64476f56..ce5de483 100644 --- a/fail2ban/tests/actiontestcase.py +++ b/fail2ban/tests/actiontestcase.py @@ -75,61 +75,59 @@ class CommandActionTest(LogCaptureTestCase): lambda: substituteRecursiveTags({'A': 'to= fromip=', 'C': '', 'B': '', 'D': ''})) self.assertRaises(ValueError, lambda: substituteRecursiveTags({'failregex': 'to= fromip=', 'sweet': '', 'honeypot': '', 'ignoreregex': ''})) - # We need here an ordered, because the sequence of iteration is very important for this test - if OrderedDict: - # No cyclic recursion, just multiple replacement of tag , should be successful: - self.assertEqual(substituteRecursiveTags( OrderedDict( - (('X', 'x=x'), ('T', '1'), ('Z', ' '), ('Y', 'y=y'))) - ), {'X': 'x=x1', 'T': '1', 'Y': 'y=y1', 'Z': 'x=x1 1 y=y1'} - ) - # No cyclic recursion, just multiple replacement of tag in composite tags, should be successful: - self.assertEqual(substituteRecursiveTags( OrderedDict( - (('X', 'x=x <> <>'), ('R1', 'Z'), ('R2', 'Y'), ('T', '1'), ('Z', ' '), ('Y', 'y=y'))) - ), {'X': 'x=x1 1 y=y1 1 y=y1 y=y1', 'R1': 'Z', 'R2': 'Y', 'T': '1', 'Z': '1 y=y1', 'Y': 'y=y1'} - ) - # No cyclic recursion, just multiple replacement of same tags, should be successful: - self.assertEqual(substituteRecursiveTags( OrderedDict(( - ('actionstart', 'ipset create hash:ip timeout family \n -I '), - ('ipmset', 'f2b-'), - ('name', 'any'), - ('bantime', '600'), - ('ipsetfamily', 'inet'), - ('iptables', 'iptables '), - ('lockingopt', '-w'), - ('chain', 'INPUT'), - ('actiontype', ''), - ('multiport', '-p -m multiport --dports -m set --match-set src -j '), - ('protocol', 'tcp'), - ('port', 'ssh'), - ('blocktype', 'REJECT',), - )) - ), OrderedDict(( - ('actionstart', 'ipset create f2b-any hash:ip timeout 600 family inet\niptables -w -I INPUT -p tcp -m multiport --dports ssh -m set --match-set f2b-any src -j REJECT'), - ('ipmset', 'f2b-any'), - ('name', 'any'), - ('bantime', '600'), - ('ipsetfamily', 'inet'), - ('iptables', 'iptables -w'), - ('lockingopt', '-w'), - ('chain', 'INPUT'), - ('actiontype', '-p tcp -m multiport --dports ssh -m set --match-set f2b-any src -j REJECT'), - ('multiport', '-p tcp -m multiport --dports ssh -m set --match-set f2b-any src -j REJECT'), - ('protocol', 'tcp'), - ('port', 'ssh'), - ('blocktype', 'REJECT') - )) - ) - # Cyclic recursion by composite tag creation, tags "create" another tag, that closes cycle: - self.assertRaises(ValueError, lambda: substituteRecursiveTags( OrderedDict(( - ('A', '<>'), - ('B', 'D'), ('C', 'E'), - ('DE', 'cycle '), - )) )) - self.assertRaises(ValueError, lambda: substituteRecursiveTags( OrderedDict(( - ('DE', 'cycle '), - ('A', '<>'), - ('B', 'D'), ('C', 'E'), - )) )) + # No cyclic recursion, just multiple replacement of tag , should be successful: + self.assertEqual(substituteRecursiveTags( OrderedDict( + (('X', 'x=x'), ('T', '1'), ('Z', ' '), ('Y', 'y=y'))) + ), {'X': 'x=x1', 'T': '1', 'Y': 'y=y1', 'Z': 'x=x1 1 y=y1'} + ) + # No cyclic recursion, just multiple replacement of tag in composite tags, should be successful: + self.assertEqual(substituteRecursiveTags( OrderedDict( + (('X', 'x=x <> <>'), ('R1', 'Z'), ('R2', 'Y'), ('T', '1'), ('Z', ' '), ('Y', 'y=y'))) + ), {'X': 'x=x1 1 y=y1 1 y=y1 y=y1', 'R1': 'Z', 'R2': 'Y', 'T': '1', 'Z': '1 y=y1', 'Y': 'y=y1'} + ) + # No cyclic recursion, just multiple replacement of same tags, should be successful: + self.assertEqual(substituteRecursiveTags( OrderedDict(( + ('actionstart', 'ipset create hash:ip timeout family \n -I '), + ('ipmset', 'f2b-'), + ('name', 'any'), + ('bantime', '600'), + ('ipsetfamily', 'inet'), + ('iptables', 'iptables '), + ('lockingopt', '-w'), + ('chain', 'INPUT'), + ('actiontype', ''), + ('multiport', '-p -m multiport --dports -m set --match-set src -j '), + ('protocol', 'tcp'), + ('port', 'ssh'), + ('blocktype', 'REJECT',), + )) + ), OrderedDict(( + ('actionstart', 'ipset create f2b-any hash:ip timeout 600 family inet\niptables -w -I INPUT -p tcp -m multiport --dports ssh -m set --match-set f2b-any src -j REJECT'), + ('ipmset', 'f2b-any'), + ('name', 'any'), + ('bantime', '600'), + ('ipsetfamily', 'inet'), + ('iptables', 'iptables -w'), + ('lockingopt', '-w'), + ('chain', 'INPUT'), + ('actiontype', '-p tcp -m multiport --dports ssh -m set --match-set f2b-any src -j REJECT'), + ('multiport', '-p tcp -m multiport --dports ssh -m set --match-set f2b-any src -j REJECT'), + ('protocol', 'tcp'), + ('port', 'ssh'), + ('blocktype', 'REJECT') + )) + ) + # Cyclic recursion by composite tag creation, tags "create" another tag, that closes cycle: + self.assertRaises(ValueError, lambda: substituteRecursiveTags( OrderedDict(( + ('A', '<>'), + ('B', 'D'), ('C', 'E'), + ('DE', 'cycle '), + )) )) + self.assertRaises(ValueError, lambda: substituteRecursiveTags( OrderedDict(( + ('DE', 'cycle '), + ('A', '<>'), + ('B', 'D'), ('C', 'E'), + )) )) # missing tags are ok self.assertEqual(substituteRecursiveTags({'A': ''}), {'A': ''}) diff --git a/fail2ban/tests/misctestcase.py b/fail2ban/tests/misctestcase.py index e2faa6fd..4b026377 100644 --- a/fail2ban/tests/misctestcase.py +++ b/fail2ban/tests/misctestcase.py @@ -70,16 +70,10 @@ class HelpersTest(unittest.TestCase): self.assertEqual(splitwords(u' 1\n 2, 3'), ['1', '2', '3']) -if sys.version_info >= (2,7): - def _sh_call(cmd): - import subprocess - ret = subprocess.check_output(cmd, shell=True) - return uni_decode(ret).rstrip() -else: - def _sh_call(cmd): - import subprocess - ret = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).stdout.read() - return uni_decode(ret).rstrip() +def _sh_call(cmd): + import subprocess + ret = subprocess.check_output(cmd, shell=True) + return uni_decode(ret).rstrip() def _getSysPythonVersion(): return _sh_call("fail2ban-python -c 'import sys; print(tuple(sys.version_info))'") @@ -92,7 +86,7 @@ class SetupTest(unittest.TestCase): unittest.F2B.SkipIfFast() setup = os.path.join(os.path.dirname(__file__), '..', '..', 'setup.py') self.setup = os.path.exists(setup) and setup or None - if not self.setup and sys.version_info >= (2,7): # pragma: no cover - running not out of the source + if not self.setup: # pragma: no cover - running not out of the source raise unittest.SkipTest( "Seems to be running not out of source distribution" " -- cannot locate setup.py") diff --git a/fail2ban/tests/observertestcase.py b/fail2ban/tests/observertestcase.py index 38cfc881..315c955a 100644 --- a/fail2ban/tests/observertestcase.py +++ b/fail2ban/tests/observertestcase.py @@ -181,7 +181,7 @@ class BanTimeIncrDB(LogCaptureTestCase): def setUp(self): """Call before every test case.""" super(BanTimeIncrDB, self).setUp() - if Fail2BanDb is None and sys.version_info >= (2,7): # pragma: no cover + if Fail2BanDb is None: # pragma: no cover raise unittest.SkipTest( "Unable to import fail2ban database module as sqlite is not " "available.") diff --git a/fail2ban/tests/servertestcase.py b/fail2ban/tests/servertestcase.py index b7ad4d32..62ae81fd 100644 --- a/fail2ban/tests/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -775,27 +775,11 @@ class Transmitter(TransmitterBase): def testPythonActionMethodsAndProperties(self): action = "TestCaseAction" - try: - out = self.transm.proceed( - ["set", self.jailName, "addaction", action, - os.path.join(TEST_FILES_DIR, "action.d", "action.py"), - '{"opt1": "value"}']) - self.assertEqual(out, (0, action)) - except AssertionError: - if ((2, 6) <= sys.version_info < (2, 6, 5)) \ - and '__init__() keywords must be strings' in out[1]: - # known issue http://bugs.python.org/issue2646 in 2.6 series - # since general Fail2Ban warnings are suppressed in normal - # operation -- let's issue Python's native warning here - import warnings - warnings.warn( - "Your version of Python %s seems to experience a known " - "issue forbidding correct operation of Fail2Ban: " - "http://bugs.python.org/issue2646 Upgrade your Python and " - "meanwhile other intestPythonActionMethodsAndProperties will " - "be skipped" % (sys.version)) - return - raise + out = self.transm.proceed( + ["set", self.jailName, "addaction", action, + os.path.join(TEST_FILES_DIR, "action.d", "action.py"), + '{"opt1": "value"}']) + self.assertEqual(out, (0, action)) self.assertSortedEqual( self.transm.proceed(["get", self.jailName, "actionproperties", action])[1],