diff --git a/fail2ban/client/configparserinc.py b/fail2ban/client/configparserinc.py index 70dfd91b..e0f39579 100644 --- a/fail2ban/client/configparserinc.py +++ b/fail2ban/client/configparserinc.py @@ -73,6 +73,17 @@ else: # pragma: no cover return self._cp_interpolate_some(option, accum, rest, section, map, *args, **kwargs) SafeConfigParser._interpolate_some = _interpolate_some +def _expandConfFilesWithLocal(filenames): + """Expands config files with local extension. + """ + newFilenames = [] + for filename in filenames: + newFilenames.append(filename) + localname = os.path.splitext(filename)[0] + '.local' + if localname not in filenames and os.path.isfile(localname): + newFilenames.append(localname) + return newFilenames + # Gets the instance of the logger. logSys = getLogger(__name__) logLevel = 7 @@ -245,6 +256,7 @@ after = 1.conf def _getIncludes(self, filenames, seen=[]): if not isinstance(filenames, list): filenames = [ filenames ] + filenames = _expandConfFilesWithLocal(filenames) # retrieve or cache include paths: if self._cfg_share: # cache/share include list: diff --git a/fail2ban/tests/clientreadertestcase.py b/fail2ban/tests/clientreadertestcase.py index 184595ab..96e6c7a4 100644 --- a/fail2ban/tests/clientreadertestcase.py +++ b/fail2ban/tests/clientreadertestcase.py @@ -28,7 +28,8 @@ import re import shutil import tempfile import unittest -from ..client.configreader import ConfigReader, ConfigReaderUnshared, NoSectionError +from ..client.configreader import ConfigReader, ConfigReaderUnshared, \ + DefinitionInitConfigReader, NoSectionError from ..client import configparserinc from ..client.jailreader import JailReader, extractOptions from ..client.filterreader import FilterReader @@ -125,6 +126,54 @@ option = %s self._remove("c.d/90.conf") self.assertEqual(self._getoption(), 2) + def testLocalInIncludes(self): + self._write("c.conf", value=None, content=""" +[INCLUDES] +before = ib.conf +after = ia.conf +[Definition] +test = %(default/test)s +""") + self._write("ib.conf", value=None, content=""" +[DEFAULT] +test = A +[Definition] +option = 1 +""") + self._write("ib.local", value=None, content=""" +[DEFAULT] +test = B +[Definition] +option = 2 +""") + self._write("ia.conf", value=None, content=""" +[DEFAULT] +test = C +[Definition] +oafter = 3 +""") + self._write("ia.local", value=None, content=""" +[DEFAULT] +test = D +[Definition] +oafter = 4 +""") + class TestDefConfReader(DefinitionInitConfigReader): + _configOpts = { + "option": ["int", None], + "oafter": ["int", None], + "test": ["string", None], + } + self.c = TestDefConfReader('c', 'option', {}) + self.c.setBaseDir(self.d) + self.assertTrue(self.c.read()) + self.c.getOptions({}, all=True) + o = self.c.getCombined() + # test local wins (overwrite all options): + self.assertEqual(o.get('option'), 2) + self.assertEqual(o.get('oafter'), 4) + self.assertEqual(o.get('test'), 'D') + def testInterpolations(self): self.assertFalse(self.c.read('i')) # nothing is there yet self._write("i.conf", value=None, content="""