diff --git a/fail2ban/client/fail2banregex.py b/fail2ban/client/fail2banregex.py index 5275a683..8e78a489 100644 --- a/fail2ban/client/fail2banregex.py +++ b/fail2ban/client/fail2banregex.py @@ -51,7 +51,7 @@ except ImportError: FilterSystemd = None from ..version import version, normVersion -from .filterreader import FilterReader +from .jailreader import FilterReader, JailReader, NoJailError from ..server.filter import Filter, FileContainer, MyTime from ..server.failregex import Regex, RegexException @@ -312,12 +312,18 @@ class Fail2banRegex(object): def _dumpRealOptions(self, reader, fltOpt): realopts = {} combopts = reader.getCombined() + if isinstance(reader, FilterReader): + _get_opt = lambda k: reader.get('Definition', k) + elif reader.filter: # JailReader for jail with filter: + _get_opt = lambda k: reader.filter.get('Definition', k) + else: # JailReader for jail without filter: + _get_opt = lambda k: None # output all options that are specified in filter-argument as well as some special (mostly interested): for k in ['logtype', 'datepattern'] + list(fltOpt.keys()): # combined options win, but they contain only a sub-set in filter expected keys, # so get the rest from definition section: try: - realopts[k] = combopts[k] if k in combopts else reader.get('Definition', k) + realopts[k] = combopts[k] if k in combopts else _get_opt(k) except NoOptionError: # pragma: no cover pass self.output("Real filter options : %r" % realopts) @@ -330,16 +336,26 @@ class Fail2banRegex(object): fltName = value fltFile = None fltOpt = {} + jail = None if regextype == 'fail': if re.search(r'(?ms)^/{0,3}[\w/_\-.]+(?:\[.*\])?$', value): try: fltName, fltOpt = extractOptions(value) + if re.search(r'(?ms)^[\w/_\-]+$', fltName): # name of jail? + try: + jail = JailReader(fltName, force_enable=True, + share_config=self.share_config) + jail.read() + except NoJailError: + jail = None if "." in fltName[~5:]: tryNames = (fltName,) else: tryNames = (fltName, fltName + '.conf', fltName + '.local') for fltFile in tryNames: - if not "/" in fltFile: + if os.path.dirname(fltFile) == 'filter.d': + fltFile = os.path.join(basedir, fltFile) + elif not "/" in fltFile: if os.path.basename(basedir) == 'filter.d': fltFile = os.path.join(basedir, fltFile) else: @@ -354,8 +370,25 @@ class Fail2banRegex(object): output(" while parsing: %s" % (value,)) if self._verbose: raise(e) return False + + readercommands = None + # if it is jail: + if jail: + self.output( "Use %11s jail : %s" % ('', fltName) ) + if fltOpt: + self.output( "Use jail/flt options : %r" % fltOpt ) + if not fltOpt: fltOpt = {} + fltOpt['backend'] = self._backend + ret = jail.getOptions(addOpts=fltOpt) + if not ret: + output('ERROR: Failed to get jail for %r' % (value,)) + return False + # show real options if expected: + if self._verbose > 1 or logSys.getEffectiveLevel()<=logging.DEBUG: + self._dumpRealOptions(jail, fltOpt) + readercommands = jail.convert(allow_no_files=True) # if it is filter file: - if fltFile is not None: + elif fltFile is not None: if (basedir == self._opts.config or os.path.basename(basedir) == 'filter.d' or ("." not in fltName[~5:] and "/" not in fltName) @@ -364,10 +397,10 @@ class Fail2banRegex(object): if os.path.basename(basedir) == 'filter.d': basedir = os.path.dirname(basedir) fltName = os.path.splitext(os.path.basename(fltName))[0] - self.output( "Use %11s filter file : %s, basedir: %s" % (regex, fltName, basedir) ) + self.output( "Use %11s file : %s, basedir: %s" % ('filter', fltName, basedir) ) else: ## foreign file - readexplicit this file and includes if possible: - self.output( "Use %11s file : %s" % (regex, fltName) ) + self.output( "Use %11s file : %s" % ('filter', fltName) ) basedir = None if not os.path.isabs(fltName): # avoid join with "filter.d" inside FilterReader fltName = os.path.abspath(fltName) @@ -398,6 +431,7 @@ class Fail2banRegex(object): # to stream: readercommands = reader.convert() + if readercommands: regex_values = {} for opt in readercommands: if opt[0] == 'multi-set': diff --git a/fail2ban/client/jailreader.py b/fail2ban/client/jailreader.py index d4ee3e8a..e7242bfd 100644 --- a/fail2ban/client/jailreader.py +++ b/fail2ban/client/jailreader.py @@ -29,16 +29,19 @@ import json import os.path import re -from .configreader import ConfigReaderUnshared, ConfigReader +from .configreader import ConfigReaderUnshared, ConfigReader, NoSectionError from .filterreader import FilterReader from .actionreader import ActionReader from ..version import version -from ..helpers import getLogger, extractOptions, splitWithOptions, splitwords +from ..helpers import _merge_dicts, getLogger, extractOptions, splitWithOptions, splitwords # Gets the instance of the logger. logSys = getLogger(__name__) +class NoJailError(ValueError): + pass + class JailReader(ConfigReader): def __init__(self, name, force_enable=False, **kwargs): @@ -64,7 +67,7 @@ class JailReader(ConfigReader): # Before returning -- verify that requested section # exists at all if not (self.__name in self.sections()): - raise ValueError("Jail %r was not found among available" + raise NoJailError("Jail %r was not found among available" % self.__name) return out @@ -117,9 +120,9 @@ class JailReader(ConfigReader): } _configOpts.update(FilterReader._configOpts) - _ignoreOpts = set(['action', 'filter', 'enabled'] + list(FilterReader._configOpts.keys())) + _ignoreOpts = set(['action', 'filter', 'enabled', 'backend'] + list(FilterReader._configOpts.keys())) - def getOptions(self): + def getOptions(self, addOpts=None): basedir = self.getBaseDir() @@ -136,6 +139,8 @@ class JailReader(ConfigReader): shouldExist=True) if not self.__opts: # pragma: no cover raise JailDefError("Init jail options failed") + if addOpts: + self.__opts = _merge_dicts(self.__opts, addOpts) if not self.isEnabled(): return True @@ -147,6 +152,8 @@ class JailReader(ConfigReader): filterName, filterOpt = extractOptions(flt) except ValueError as e: raise JailDefError("Invalid filter definition %r: %s" % (flt, e)) + if addOpts: + filterOpt = _merge_dicts(filterOpt, addOpts) self.__filter = FilterReader( filterName, self.__name, filterOpt, share_config=self.share_config, basedir=basedir) @@ -219,6 +226,15 @@ class JailReader(ConfigReader): return False return True + @property + def filter(self): + return self.__filter + + def getCombined(self): + if not self.__filter: + return self.__opts + return _merge_dicts(self.__opts, self.__filter.getCombined()) + def convert(self, allow_no_files=False): """Convert read before __opts to the commands stream @@ -240,9 +256,10 @@ class JailReader(ConfigReader): stream.extend(self.__filter.convert()) # and using options from jail: FilterReader._fillStream(stream, self.__opts, self.__name) + backend = self.__opts.get('backend', 'auto') for opt, value in self.__opts.items(): if opt == "logpath": - if self.__opts.get('backend', '').startswith("systemd"): continue + if backend.startswith("systemd"): continue found_files = 0 for path in value.split("\n"): path = path.rsplit(" ", 1) @@ -260,8 +277,6 @@ class JailReader(ConfigReader): if not allow_no_files: raise ValueError(msg) logSys.warning(msg) - elif opt == "backend": - backend = value elif opt == "ignoreip": stream.append(["set", self.__name, "addignoreip"] + splitwords(value)) elif opt not in JailReader._ignoreOpts: