mirror of https://github.com/fail2ban/fail2ban
bug fix in the config readers: mixing with the init section should affect only own init options (from init section only bypass default section);
the situation details: value of "_daemon" from default section "default" (with init section) falsely overwrites it from definition section "test" - the resulting value of "_daemon" should be "test" in all 3 resulting failregex's (as specified in test.local), fixed and covered now; additionally more complex cases covered also (all filter parameters in jail via "%(known/...)s", dynamical interpolation across all, etc);pull/1726/head
parent
4f1473724b
commit
57e9c25449
|
@ -32,7 +32,7 @@ from ..helpers import getLogger
|
||||||
if sys.version_info >= (3,2):
|
if sys.version_info >= (3,2):
|
||||||
|
|
||||||
# SafeConfigParser deprecated from Python 3.2 (renamed to ConfigParser)
|
# SafeConfigParser deprecated from Python 3.2 (renamed to ConfigParser)
|
||||||
from configparser import ConfigParser as SafeConfigParser, \
|
from configparser import ConfigParser as SafeConfigParser, NoSectionError, \
|
||||||
BasicInterpolation
|
BasicInterpolation
|
||||||
|
|
||||||
# And interpolation of __name__ was simply removed, thus we need to
|
# And interpolation of __name__ was simply removed, thus we need to
|
||||||
|
@ -60,7 +60,7 @@ if sys.version_info >= (3,2):
|
||||||
parser, option, accum, rest, section, map, depth)
|
parser, option, accum, rest, section, map, depth)
|
||||||
|
|
||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
from ConfigParser import SafeConfigParser
|
from ConfigParser import SafeConfigParser, NoSectionError
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = getLogger(__name__)
|
logSys = getLogger(__name__)
|
||||||
|
@ -200,6 +200,18 @@ after = 1.conf
|
||||||
def get_sections(self):
|
def get_sections(self):
|
||||||
return self._sections
|
return self._sections
|
||||||
|
|
||||||
|
def options(self, section, onlyOwn=False):
|
||||||
|
"""Return a list of option names for the given section name."""
|
||||||
|
try:
|
||||||
|
opts = self._sections[section]
|
||||||
|
except KeyError:
|
||||||
|
raise NoSectionError(section)
|
||||||
|
if not onlyOwn:
|
||||||
|
# mix it with defaults:
|
||||||
|
return set(opts.keys()) | set(self._defaults)
|
||||||
|
# only own option names:
|
||||||
|
return opts.keys()
|
||||||
|
|
||||||
def read(self, filenames, get_includes=True):
|
def read(self, filenames, get_includes=True):
|
||||||
if not isinstance(filenames, list):
|
if not isinstance(filenames, list):
|
||||||
filenames = [ filenames ]
|
filenames = [ filenames ]
|
||||||
|
|
|
@ -122,9 +122,9 @@ class ConfigReader():
|
||||||
if self._cfg is not None:
|
if self._cfg is not None:
|
||||||
return self._cfg.merge_section(*args, **kwargs)
|
return self._cfg.merge_section(*args, **kwargs)
|
||||||
|
|
||||||
def options(self, *args):
|
def options(self, section, onlyOwn=False):
|
||||||
if self._cfg is not None:
|
if self._cfg is not None:
|
||||||
return self._cfg.options(*args)
|
return self._cfg.options(section, onlyOwn)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get(self, sec, opt, raw=False, vars={}):
|
def get(self, sec, opt, raw=False, vars={}):
|
||||||
|
@ -297,23 +297,35 @@ class DefinitionInitConfigReader(ConfigReader):
|
||||||
self._create_unshared(self._file)
|
self._create_unshared(self._file)
|
||||||
return SafeConfigParserWithIncludes.read(self._cfg, self._file)
|
return SafeConfigParserWithIncludes.read(self._cfg, self._file)
|
||||||
|
|
||||||
def getOptions(self, pOpts):
|
def getOptions(self, pOpts, all=False):
|
||||||
# overwrite static definition options with init values, supplied as
|
# overwrite static definition options with init values, supplied as
|
||||||
# direct parameters from jail-config via action[xtra1="...", xtra2=...]:
|
# direct parameters from jail-config via action[xtra1="...", xtra2=...]:
|
||||||
if self._initOpts:
|
|
||||||
if not pOpts:
|
if not pOpts:
|
||||||
pOpts = dict()
|
pOpts = dict()
|
||||||
|
if self._initOpts:
|
||||||
pOpts = _merge_dicts(pOpts, self._initOpts)
|
pOpts = _merge_dicts(pOpts, self._initOpts)
|
||||||
self._opts = ConfigReader.getOptions(
|
self._opts = ConfigReader.getOptions(
|
||||||
self, "Definition", self._configOpts, pOpts)
|
self, "Definition", self._configOpts, pOpts)
|
||||||
self._pOpts = pOpts
|
self._pOpts = pOpts
|
||||||
if self.has_section("Init"):
|
if self.has_section("Init"):
|
||||||
for opt in self.options("Init"):
|
# get only own options (without options from default):
|
||||||
v = self.get("Init", opt)
|
getopt = lambda opt: self.get("Init", opt)
|
||||||
if not opt.startswith('known/') and opt != '__name__':
|
for opt in self.options("Init", onlyOwn=True):
|
||||||
|
if opt == '__name__': continue
|
||||||
|
v = None
|
||||||
|
if not opt.startswith('known/'):
|
||||||
|
if v is None: v = getopt(opt)
|
||||||
self._initOpts['known/'+opt] = v
|
self._initOpts['known/'+opt] = v
|
||||||
if not opt in self._initOpts:
|
if opt not in self._initOpts:
|
||||||
|
if v is None: v = getopt(opt)
|
||||||
self._initOpts[opt] = v
|
self._initOpts[opt] = v
|
||||||
|
if all and self.has_section("Definition"):
|
||||||
|
# merge with all definition options (and options from default),
|
||||||
|
# bypass already converted option (so merge only new options):
|
||||||
|
for opt in self.options("Definition"):
|
||||||
|
if opt == '__name__' or opt in self._opts: continue
|
||||||
|
self._opts[opt] = self.get("Definition", opt)
|
||||||
|
|
||||||
|
|
||||||
def _convert_to_boolean(self, value):
|
def _convert_to_boolean(self, value):
|
||||||
return value.lower() in ("1", "yes", "true", "on")
|
return value.lower() in ("1", "yes", "true", "on")
|
||||||
|
@ -336,12 +348,12 @@ class DefinitionInitConfigReader(ConfigReader):
|
||||||
|
|
||||||
def getCombined(self, ignore=()):
|
def getCombined(self, ignore=()):
|
||||||
combinedopts = self._opts
|
combinedopts = self._opts
|
||||||
ignore = set(ignore).copy()
|
|
||||||
if self._initOpts:
|
if self._initOpts:
|
||||||
combinedopts = _merge_dicts(self._opts, self._initOpts)
|
combinedopts = _merge_dicts(combinedopts, self._initOpts)
|
||||||
if not len(combinedopts):
|
if not len(combinedopts):
|
||||||
return {}
|
return {}
|
||||||
# ignore conditional options:
|
# ignore conditional options:
|
||||||
|
ignore = set(ignore).copy()
|
||||||
for n in combinedopts:
|
for n in combinedopts:
|
||||||
cond = SafeConfigParserWithIncludes.CONDITIONAL_RE.match(n)
|
cond = SafeConfigParserWithIncludes.CONDITIONAL_RE.match(n)
|
||||||
if cond:
|
if cond:
|
||||||
|
|
|
@ -139,11 +139,11 @@ class JailReader(ConfigReader):
|
||||||
filterName, self.__name, filterOpt,
|
filterName, self.__name, filterOpt,
|
||||||
share_config=self.share_config, basedir=self.getBaseDir())
|
share_config=self.share_config, basedir=self.getBaseDir())
|
||||||
ret = self.__filter.read()
|
ret = self.__filter.read()
|
||||||
# merge options from filter as 'known/...':
|
|
||||||
self.__filter.getOptions(self.__opts)
|
|
||||||
ConfigReader.merge_section(self, self.__name, self.__filter.getCombined(), 'known/')
|
|
||||||
if not ret:
|
if not ret:
|
||||||
raise JailDefError("Unable to read the filter %r" % filterName)
|
raise JailDefError("Unable to read the filter %r" % filterName)
|
||||||
|
# merge options from filter as 'known/...' (all options unfiltered):
|
||||||
|
self.__filter.getOptions(self.__opts, all=True)
|
||||||
|
ConfigReader.merge_section(self, self.__name, self.__filter.getCombined(), 'known/')
|
||||||
else:
|
else:
|
||||||
self.__filter = None
|
self.__filter = None
|
||||||
logSys.warning("No filter set for jail %s" % self.__name)
|
logSys.warning("No filter set for jail %s" % self.__name)
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
#[INCLUDES]
|
#[INCLUDES]
|
||||||
#before = common.conf
|
#before = common.conf
|
||||||
|
|
||||||
[Definition]
|
[DEFAULT]
|
||||||
failregex = failure test 1 (filter.d/test.conf) <HOST>
|
_daemon = default
|
||||||
|
|
||||||
|
[Definition]
|
||||||
|
where = conf
|
||||||
|
failregex = failure <_daemon> <one> (filter.d/test.%(where)s) <HOST>
|
||||||
|
|
||||||
|
[Init]
|
||||||
|
# test parameter, should be overriden in jail by "filter=test[one=1,...]"
|
||||||
|
one = *1*
|
||||||
|
|
|
@ -2,6 +2,15 @@
|
||||||
#before = common.conf
|
#before = common.conf
|
||||||
|
|
||||||
[Definition]
|
[Definition]
|
||||||
|
# overwrite default daemon, additionally it should be accessible in jail with "%(known/_daemon)s":
|
||||||
|
_daemon = test
|
||||||
|
# interpolate previous regex (from test.conf) + new 2nd + dynamical substitution) of "two" an "where":
|
||||||
failregex = %(known/failregex)s
|
failregex = %(known/failregex)s
|
||||||
failure test 2 (filter.d/test.local) <HOST>
|
failure %(_daemon)s <two> (filter.d/test.<where>) <HOST>
|
||||||
|
# parameter "two" should be specified in jail by "filter=test[..., two=2]"
|
||||||
|
|
||||||
|
[Init]
|
||||||
|
# this parameter can be used in jail with "%(known/three)s":
|
||||||
|
three = 3
|
||||||
|
# this parameter "where" does not overwrite "where" in definition of test.conf (dynamical values only):
|
||||||
|
where = local
|
|
@ -15,9 +15,9 @@ ignoreip =
|
||||||
|
|
||||||
[test-known-interp]
|
[test-known-interp]
|
||||||
enabled = true
|
enabled = true
|
||||||
filter = test
|
filter = test[one=1,two=2]
|
||||||
failregex = %(known/failregex)s
|
failregex = %(known/failregex)s
|
||||||
failure test 3 (jail.local) <HOST>
|
failure %(known/_daemon)s %(known/three)s (jail.local) <HOST>
|
||||||
|
|
||||||
[missinglogfiles]
|
[missinglogfiles]
|
||||||
enabled = true
|
enabled = true
|
||||||
|
|
Loading…
Reference in New Issue