diff --git a/fail2ban/client/filterreader.py b/fail2ban/client/filterreader.py index 9edeb2f3..9231261f 100644 --- a/fail2ban/client/filterreader.py +++ b/fail2ban/client/filterreader.py @@ -39,7 +39,7 @@ class FilterReader(DefinitionInitConfigReader): _configOpts = { "prefregex": ["string", None], "ignoreregex": ["string", None], - "failregex": ["string", ""], + "failregex": ["string", None], "maxlines": ["int", None], "datepattern": ["string", None], "journalmatch": ["string", None], @@ -57,6 +57,10 @@ class FilterReader(DefinitionInitConfigReader): opts = self.getCombined() if not len(opts): return stream + return FilterReader._fillStream(stream, opts, self._jailName) + + @staticmethod + def _fillStream(stream, opts, jailName): for opt, value in opts.iteritems(): if opt in ("failregex", "ignoreregex"): if value is None: continue @@ -66,21 +70,20 @@ class FilterReader(DefinitionInitConfigReader): if regex != '': multi.append(regex) if len(multi) > 1: - stream.append(["multi-set", self._jailName, "add" + opt, multi]) + stream.append(["multi-set", jailName, "add" + opt, multi]) elif len(multi): - stream.append(["set", self._jailName, "add" + opt, multi[0]]) + stream.append(["set", jailName, "add" + opt, multi[0]]) elif opt in ('maxlines', 'prefregex'): # Be sure we set this options first. - stream.insert(0, ["set", self._jailName, opt, value]) + stream.insert(0, ["set", jailName, opt, value]) elif opt in ('datepattern'): - stream.append(["set", self._jailName, opt, value]) - # Do not send a command if the match is empty. + stream.append(["set", jailName, opt, value]) elif opt == 'journalmatch': + # Do not send a command if the match is empty. if value is None: continue for match in value.split("\n"): if match == '': continue stream.append( - ["set", self._jailName, "addjournalmatch"] + - shlex.split(match)) + ["set", jailName, "addjournalmatch"] + shlex.split(match)) return stream diff --git a/fail2ban/client/jailreader.py b/fail2ban/client/jailreader.py index d1256b56..c8ab6416 100644 --- a/fail2ban/client/jailreader.py +++ b/fail2ban/client/jailreader.py @@ -86,29 +86,32 @@ class JailReader(ConfigReader): logSys.warning("File %s is a dangling link, thus cannot be monitored" % p) return pathList + _configOpts1st = { + "enabled": ["bool", False], + "backend": ["string", "auto"], + "filter": ["string", ""] + } + _configOpts = { + "enabled": ["bool", False], + "backend": ["string", "auto"], + "maxretry": ["int", None], + "maxmatches": ["int", None], + "findtime": ["string", None], + "bantime": ["string", None], + "usedns": ["string", None], # be sure usedns is before all regex(s) in stream + "ignorecommand": ["string", None], + "ignoreself": ["bool", None], + "ignoreip": ["string", None], + "ignorecache": ["string", None], + "filter": ["string", ""], + "logtimezone": ["string", None], + "logencoding": ["string", None], + "logpath": ["string", None], # logpath after all log-related data (backend, date-pattern, etc) + "action": ["string", ""] + } + _configOpts.update(FilterReader._configOpts) + def getOptions(self): - opts1st = [["bool", "enabled", False], - ["string", "backend", "auto"], - ["string", "filter", ""]] - opts = [["bool", "enabled", False], - ["string", "backend", "auto"], - ["int", "maxretry", None], - ["int", "maxmatches", None], - ["string", "findtime", None], - ["string", "bantime", None], - ["string", "usedns", None], # be sure usedns is before all regex(s) in stream - ["string", "failregex", None], - ["string", "ignoreregex", None], - ["string", "ignorecommand", None], - ["bool", "ignoreself", None], - ["string", "ignoreip", None], - ["string", "ignorecache", None], - ["string", "filter", ""], - ["string", "datepattern", None], - ["string", "logtimezone", None], - ["string", "logencoding", None], - ["string", "logpath", None], # logpath after all log-related data (backend, date-pattern, etc) - ["string", "action", ""]] # Before interpolation (substitution) add static options always available as default: self.merge_defaults({ @@ -118,7 +121,8 @@ class JailReader(ConfigReader): try: # Read first options only needed for merge defaults ('known/...' from filter): - self.__opts = ConfigReader.getOptions(self, self.__name, opts1st, shouldExist=True) + self.__opts = ConfigReader.getOptions(self, self.__name, self._configOpts1st, + shouldExist=True) if not self.__opts: # pragma: no cover raise JailDefError("Init jail options failed") @@ -150,7 +154,7 @@ class JailReader(ConfigReader): logSys.warning("No filter set for jail %s" % self.__name) # Read second all options (so variables like %(known/param) can be interpolated): - self.__opts = ConfigReader.getOptions(self, self.__name, opts) + self.__opts = ConfigReader.getOptions(self, self.__name, self._configOpts) if not self.__opts: # pragma: no cover raise JailDefError("Read jail options failed") @@ -227,8 +231,11 @@ class JailReader(ConfigReader): if e: stream.extend([['config-error', "Jail '%s' skipped, because of wrong configuration: %s" % (self.__name, e)]]) return stream + # fill jail with filter options, using filter (only not overriden in jail): if self.__filter: stream.extend(self.__filter.convert()) + # and using options from jail: + FilterReader._fillStream(stream, self.__opts, self.__name) for opt, value in self.__opts.iteritems(): if opt == "logpath": if self.__opts.get('backend', '').startswith("systemd"): continue @@ -255,17 +262,8 @@ class JailReader(ConfigReader): backend = value elif opt == "ignoreip": stream.append(["set", self.__name, "addignoreip"] + splitwords(value)) - elif opt in ("failregex", "ignoreregex"): - multi = [] - for regex in value.split('\n'): - # Do not send a command if the rule is empty. - if regex != '': - multi.append(regex) - if len(multi) > 1: - stream.append(["multi-set", self.__name, "add" + opt, multi]) - elif len(multi): - stream.append(["set", self.__name, "add" + opt, multi[0]]) - elif opt not in ('action', 'filter', 'enabled'): + elif (opt not in ('action', 'filter', 'enabled') + and opt not in FilterReader._configOpts): stream.append(["set", self.__name, opt, value]) for action in self.__actions: if isinstance(action, (ConfigReaderUnshared, ConfigReader)): diff --git a/fail2ban/tests/clientreadertestcase.py b/fail2ban/tests/clientreadertestcase.py index 8320370b..2eaa6e0f 100644 --- a/fail2ban/tests/clientreadertestcase.py +++ b/fail2ban/tests/clientreadertestcase.py @@ -302,6 +302,25 @@ class JailReaderTest(LogCaptureTestCase): self.assertEqual(jail.getName(), 'sshd') jail.setName('ssh-funky-blocker') self.assertEqual(jail.getName(), 'ssh-funky-blocker') + + def testOverrideFilterOptInJail(self): + unittest.F2B.SkipIfCfgMissing(stock=True); # expected include of common.conf + jail = JailReader('sshd-override-flt-opts', basedir=IMPERFECT_CONFIG, + share_config=IMPERFECT_CONFIG_SHARE_CFG, force_enable=True) + self.assertTrue(jail.read()) + self.assertTrue(jail.getOptions()) + self.assertTrue(jail.isEnabled()) + stream = jail.convert() + # check filter options are overriden with values specified directly in jail: + # prefregex: + self.assertEqual([['set', 'sshd-override-flt-opts', 'prefregex', '^Test']], + [o for o in stream if len(o) > 2 and o[2] == 'prefregex']) + # journalmatch: + self.assertEqual([['set', 'sshd-override-flt-opts', 'addjournalmatch', '_COMM=test']], + [o for o in stream if len(o) > 2 and o[2] == 'addjournalmatch']) + # maxlines: + self.assertEqual([['set', 'sshd-override-flt-opts', 'maxlines', 2]], + [o for o in stream if len(o) > 2 and o[2] == 'maxlines']) def testSplitOption(self): # Simple example diff --git a/fail2ban/tests/config/jail.conf b/fail2ban/tests/config/jail.conf index 6539adc1..a9e45fdf 100644 --- a/fail2ban/tests/config/jail.conf +++ b/fail2ban/tests/config/jail.conf @@ -62,4 +62,13 @@ log2nd = %(logpath)s d.log action = action[actname='ban'] action[actname='log', logpath="%(log2nd)s"] - action[actname='test'] \ No newline at end of file + action[actname='test'] + +[sshd-override-flt-opts] +filter = zzz-sshd-obsolete-multiline[logtype=short] +backend = systemd +prefregex = ^Test +failregex = ^Test unused $ +journalmatch = _COMM=test +maxlines = 2 +enabled = false