mirror of https://github.com/fail2ban/fail2ban
Config reader's: introduced new syntax `%(section/option)s`, in opposite to extended interpolation of python 3 `${section:option}` work with all supported python version in fail2ban and this syntax is like our another features like `%(known/option)s`, etc.;
Variable `default_backend` switched to `%(default/backend)s`, so totally backwards compatible now, but now the setting of parameter `backend` in default section of `jail.local` can overwrite default backend also. Test cases extended: test targeted section options "section/option" (default and cross sections options);pull/1750/head
parent
5ce8d4f741
commit
e52f483557
|
@ -7,7 +7,7 @@ after = paths-overrides.local
|
||||||
|
|
||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
|
|
||||||
default_backend = %(known/backend)s
|
default_backend = %(default/backend)s
|
||||||
|
|
||||||
sshd_log = %(syslog_authpriv)s
|
sshd_log = %(syslog_authpriv)s
|
||||||
sshd_backend = %(default_backend)s
|
sshd_backend = %(default_backend)s
|
||||||
|
|
|
@ -56,14 +56,8 @@ if sys.version_info >= (3,2):
|
||||||
if section and not (__name__ in map):
|
if section and not (__name__ in map):
|
||||||
map = map.copy() # just to be safe
|
map = map.copy() # just to be safe
|
||||||
map['__name__'] = section
|
map['__name__'] = section
|
||||||
try:
|
# try to wrap section options like %(section/option)s:
|
||||||
return super(BasicInterpolationWithName, self)._interpolate_some(
|
parser._map_section_options(section, option, rest, map)
|
||||||
parser, option, accum, rest, section, map, *args, **kwargs)
|
|
||||||
except InterpolationMissingOptionError as e:
|
|
||||||
# fallback: try to wrap missing default options as "known/options":
|
|
||||||
if not parser._map_known_defaults(section, option, rest, map): # pragma: no cover
|
|
||||||
raise e
|
|
||||||
# try again:
|
|
||||||
return super(BasicInterpolationWithName, self)._interpolate_some(
|
return super(BasicInterpolationWithName, self)._interpolate_some(
|
||||||
parser, option, accum, rest, section, map, *args, **kwargs)
|
parser, option, accum, rest, section, map, *args, **kwargs)
|
||||||
|
|
||||||
|
@ -74,16 +68,16 @@ else: # pragma: no cover
|
||||||
# Interpolate missing known/option as option from default section
|
# Interpolate missing known/option as option from default section
|
||||||
SafeConfigParser._cp_interpolate_some = SafeConfigParser._interpolate_some
|
SafeConfigParser._cp_interpolate_some = SafeConfigParser._interpolate_some
|
||||||
def _interpolate_some(self, option, accum, rest, section, map, *args, **kwargs):
|
def _interpolate_some(self, option, accum, rest, section, map, *args, **kwargs):
|
||||||
try:
|
# try to wrap section options like %(section/option)s:
|
||||||
return self._cp_interpolate_some(option, accum, rest, section, map, *args, **kwargs)
|
self._map_section_options(section, option, rest, map)
|
||||||
except InterpolationMissingOptionError as e:
|
|
||||||
# fallback: try to wrap missing default options as "known/options":
|
|
||||||
if self._map_known_defaults(section, option, rest, map): # pragma: no cover
|
|
||||||
raise e
|
|
||||||
# try again:
|
|
||||||
return self._cp_interpolate_some(option, accum, rest, section, map, *args, **kwargs)
|
return self._cp_interpolate_some(option, accum, rest, section, map, *args, **kwargs)
|
||||||
SafeConfigParser._interpolate_some = _interpolate_some
|
SafeConfigParser._interpolate_some = _interpolate_some
|
||||||
|
|
||||||
|
try:
|
||||||
|
from configparser import _UNSET
|
||||||
|
except ImportError:
|
||||||
|
_UNSET = object()
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = getLogger(__name__)
|
logSys = getLogger(__name__)
|
||||||
logLevel = 7
|
logLevel = 7
|
||||||
|
@ -123,7 +117,8 @@ after = 1.conf
|
||||||
|
|
||||||
SECTION_NAME = "INCLUDES"
|
SECTION_NAME = "INCLUDES"
|
||||||
|
|
||||||
_KNOWN_OPTSUBST_CRE = re.compile(r'%\(known/([^\)]+)\)s')
|
SECTION_OPT_CRE = re.compile(r'^(\w+)/(.+)$')
|
||||||
|
SECTION_OPTSUBST_CRE = re.compile(r'%\((\w+/([^\)]+))\)s')
|
||||||
|
|
||||||
CONDITIONAL_RE = re.compile(r"^(\w+)(\?.+)$")
|
CONDITIONAL_RE = re.compile(r"^(\w+)(\?.+)$")
|
||||||
|
|
||||||
|
@ -142,22 +137,43 @@ after = 1.conf
|
||||||
SafeConfigParser.__init__(self, *args, **kwargs)
|
SafeConfigParser.__init__(self, *args, **kwargs)
|
||||||
self._cfg_share = share_config
|
self._cfg_share = share_config
|
||||||
|
|
||||||
def _map_known_defaults(self, section, option, rest, map):
|
def _map_section_options(self, section, option, rest, map):
|
||||||
""" Fallback: try to wrap missing default options as "known/options"
|
|
||||||
"""
|
"""
|
||||||
known = SafeConfigParserWithIncludes._KNOWN_OPTSUBST_CRE.findall(rest)
|
Interpolates values of the section options (name syntax `%(section/option)s`).
|
||||||
if not known: # pragma: no cover
|
|
||||||
|
Fallback: try to wrap missing default options as "default/options" resp. "known/options"
|
||||||
|
"""
|
||||||
|
if '/' not in rest or '%(' not in rest: # pragma: no cover
|
||||||
return 0
|
return 0
|
||||||
for opt in known:
|
soptrep = SafeConfigParserWithIncludes.SECTION_OPTSUBST_CRE.findall(rest)
|
||||||
kopt = 'known/'+opt
|
if not soptrep: # pragma: no cover
|
||||||
if kopt not in map:
|
return 0
|
||||||
|
for sopt, opt in soptrep:
|
||||||
|
if sopt not in map:
|
||||||
|
sec = sopt[:~len(opt)]
|
||||||
|
seclwr = sec.lower()
|
||||||
|
if seclwr != 'default':
|
||||||
|
if seclwr == 'known':
|
||||||
|
# try get raw value from known options:
|
||||||
|
try:
|
||||||
|
v = self._sections['KNOWN'][opt]
|
||||||
|
except KeyError:
|
||||||
|
# fallback to default:
|
||||||
try:
|
try:
|
||||||
v = self._defaults[opt]
|
v = self._defaults[opt]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
continue
|
continue
|
||||||
self._defaults[kopt] = v
|
else:
|
||||||
try: # for python 2.6 we should duplicate it in map-vars also:
|
# get substituted value of opt in section:
|
||||||
map[kopt] = v
|
v = self.get(sec, opt)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
v = self._defaults[opt]
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
self._defaults[sopt] = v
|
||||||
|
try: # for some python versions need to duplicate it in map-vars also:
|
||||||
|
map[sopt] = v
|
||||||
except: pass
|
except: pass
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
@ -244,6 +260,18 @@ after = 1.conf
|
||||||
def get_sections(self):
|
def get_sections(self):
|
||||||
return self._sections
|
return self._sections
|
||||||
|
|
||||||
|
def get(self, sec, opt, raw=False, vars={}, fallback=_UNSET):
|
||||||
|
try:
|
||||||
|
return SafeConfigParser.get(self, sec, opt, raw=raw, vars=vars)
|
||||||
|
except:
|
||||||
|
sopt = SafeConfigParserWithIncludes.SECTION_OPT_CRE.match(opt)
|
||||||
|
if not sopt: raise
|
||||||
|
sec, opt = sopt.groups()
|
||||||
|
if sec.lower() == 'default':
|
||||||
|
# get default raw value:
|
||||||
|
return self._defaults[opt]
|
||||||
|
return SafeConfigParser.get(self, sec, opt, raw=raw, vars=vars)
|
||||||
|
|
||||||
def options(self, section, withDefault=True):
|
def options(self, section, withDefault=True):
|
||||||
"""Return a list of option names for the given section name.
|
"""Return a list of option names for the given section name.
|
||||||
|
|
||||||
|
@ -303,11 +331,7 @@ after = 1.conf
|
||||||
s2 = alls.get(n)
|
s2 = alls.get(n)
|
||||||
if isinstance(s2, dict):
|
if isinstance(s2, dict):
|
||||||
# save previous known values, for possible using in local interpolations later:
|
# save previous known values, for possible using in local interpolations later:
|
||||||
sk = {}
|
self.merge_section('KNOWN', s2, '')
|
||||||
for k, v in s2.iteritems():
|
|
||||||
if not k.startswith('known/') and k != '__name__':
|
|
||||||
sk['known/'+k] = v
|
|
||||||
s2.update(sk)
|
|
||||||
# merge section
|
# merge section
|
||||||
s2.update(s)
|
s2.update(s)
|
||||||
else:
|
else:
|
||||||
|
@ -324,14 +348,18 @@ after = 1.conf
|
||||||
else:
|
else:
|
||||||
return SafeConfigParser.read(self, fileNamesFull)
|
return SafeConfigParser.read(self, fileNamesFull)
|
||||||
|
|
||||||
def merge_section(self, section, options, pref='known/'):
|
def merge_section(self, section, options, pref=None):
|
||||||
alls = self.get_sections()
|
alls = self.get_sections()
|
||||||
if pref == '':
|
try:
|
||||||
alls[section].update(options)
|
sec = alls[section]
|
||||||
|
except KeyError:
|
||||||
|
alls[section] = sec = dict()
|
||||||
|
if not pref:
|
||||||
|
sec.update(options)
|
||||||
return
|
return
|
||||||
sk = {}
|
sk = {}
|
||||||
for k, v in options.iteritems():
|
for k, v in options.iteritems():
|
||||||
if not k.startswith(pref) and k != '__name__':
|
if not k.startswith(pref) and k != '__name__':
|
||||||
sk[pref+k] = v
|
sk[pref+k] = v
|
||||||
alls[section].update(sk)
|
sec.update(sk)
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,7 @@ class ConfigReader():
|
||||||
|
|
||||||
def sections(self):
|
def sections(self):
|
||||||
try:
|
try:
|
||||||
return self._cfg.sections()
|
return (n for n in self._cfg.sections() if n != 'KNOWN')
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
|
@ -162,6 +162,33 @@ c = d ;in line comment
|
||||||
self.assertEqual(self.c.get('DEFAULT', 'b'), 'a')
|
self.assertEqual(self.c.get('DEFAULT', 'b'), 'a')
|
||||||
self.assertEqual(self.c.get('DEFAULT', 'c'), 'd')
|
self.assertEqual(self.c.get('DEFAULT', 'c'), 'd')
|
||||||
|
|
||||||
|
def testTargetedSectionOptions(self):
|
||||||
|
self.assertFalse(self.c.read('g')) # nothing is there yet
|
||||||
|
self._write("g.conf", value=None, content="""
|
||||||
|
[DEFAULT]
|
||||||
|
a = def-a
|
||||||
|
b = def-b,a:`%(a)s`
|
||||||
|
c = def-c,b:"%(b)s"
|
||||||
|
d = def-d-b:"%(known/b)s"
|
||||||
|
[jail]
|
||||||
|
a = jail-a-%(test/a)s
|
||||||
|
b = jail-b-%(test/b)s
|
||||||
|
[test]
|
||||||
|
a = test-a-%(default/a)s
|
||||||
|
b = test-b-%(known/b)s
|
||||||
|
""")
|
||||||
|
self.assertTrue(self.c.read('g'))
|
||||||
|
self.assertEqual(self.c.get('test', 'a'), 'test-a-def-a')
|
||||||
|
self.assertEqual(self.c.get('test', 'b'), 'test-b-def-b,a:`test-a-def-a`')
|
||||||
|
self.assertEqual(self.c.get('jail', 'a'), 'jail-a-test-a-def-a')
|
||||||
|
self.assertEqual(self.c.get('jail', 'b'), 'jail-b-test-b-def-b,a:`test-a-def-a`')
|
||||||
|
self.assertEqual(self.c.get('jail', 'c'), 'def-c,b:"jail-b-test-b-def-b,a:`test-a-def-a`"')
|
||||||
|
self.assertEqual(self.c.get('jail', 'd'), 'def-d-b:"def-b,a:`jail-a-test-a-def-a`"')
|
||||||
|
self.assertEqual(self.c.get('test', 'c'), 'def-c,b:"test-b-def-b,a:`test-a-def-a`"')
|
||||||
|
self.assertEqual(self.c.get('test', 'd'), 'def-d-b:"def-b,a:`test-a-def-a`"')
|
||||||
|
self.assertEqual(self.c.get('DEFAULT', 'c'), 'def-c,b:"def-b,a:`def-a`"')
|
||||||
|
self.assertEqual(self.c.get('DEFAULT', 'd'), 'def-d-b:"def-b,a:`def-a`"')
|
||||||
|
|
||||||
|
|
||||||
class JailReaderTest(LogCaptureTestCase):
|
class JailReaderTest(LogCaptureTestCase):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue