From b698a749029af2c216268aa29a1228f21e87bfb7 Mon Sep 17 00:00:00 2001 From: sebres Date: Thu, 7 Sep 2017 19:07:06 +0200 Subject: [PATCH 1/3] introduces new command-line options `--dp`, `--dump-pretty` to dump the configuration using more human readable representation; allow dump of configuration, also if log-file is not available (warning only) --- fail2ban/client/configurator.py | 4 ++-- fail2ban/client/fail2bancmdline.py | 26 +++++++++++++++++------- fail2ban/client/jailreader.py | 9 +++++--- fail2ban/tests/fail2banclienttestcase.py | 6 +++++- 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/fail2ban/client/configurator.py b/fail2ban/client/configurator.py index e8472ac1..fe735251 100644 --- a/fail2ban/client/configurator.py +++ b/fail2ban/client/configurator.py @@ -76,9 +76,9 @@ class Configurator: self.__fail2ban.getOptions(updateMainOpt) return self.__jails.getOptions(jail, ignoreWrong=ignoreWrong) - def convertToProtocol(self): + def convertToProtocol(self, allow_no_files=False): self.__streams["general"] = self.__fail2ban.convert() - self.__streams["jails"] = self.__jails.convert() + self.__streams["jails"] = self.__jails.convert(allow_no_files=allow_no_files) def getConfigStream(self): cmds = list() diff --git a/fail2ban/client/fail2bancmdline.py b/fail2ban/client/fail2bancmdline.py index f213d037..401aa9b6 100644 --- a/fail2ban/client/fail2bancmdline.py +++ b/fail2ban/client/fail2bancmdline.py @@ -102,6 +102,7 @@ class Fail2banCmdLine(): output(" --logtarget |STDOUT|STDERR|SYSLOG") output(" --syslogsocket auto|") output(" -d dump configuration. For debugging") + output(" --dp, --dump-pretty dump the configuration using more human readable representation") output(" -t, --test test configuration (can be also specified with start parameters)") output(" -i interactive mode") output(" -v increase verbosity") @@ -137,8 +138,8 @@ class Fail2banCmdLine(): self._conf["pidfile"] = opt[1] elif o.startswith("--log") or o.startswith("--sys"): self._conf[ o[2:] ] = opt[1] - elif o == "-d": - self._conf["dump"] = True + elif o in ["-d", "--dp", "--dump-pretty"]: + self._conf["dump"] = True if o == "-d" else 2 elif o == "-t" or o == "--test": self.cleanConfOnly = True self._conf["test"] = True @@ -184,7 +185,8 @@ class Fail2banCmdLine(): # Reads the command line options. try: cmdOpts = 'hc:s:p:xfbdtviqV' - cmdLongOpts = ['loglevel=', 'logtarget=', 'syslogsocket=', 'test', 'async', 'timeout=', 'str2sec=', 'help', 'version'] + cmdLongOpts = ['loglevel=', 'logtarget=', 'syslogsocket=', 'test', 'async', + 'timeout=', 'str2sec=', 'help', 'version', 'dp', '--dump-pretty'] optList, self._args = getopt.getopt(self._argv[1:], cmdOpts, cmdLongOpts) except getopt.GetoptError: self.dispUsage() @@ -240,7 +242,10 @@ class Fail2banCmdLine(): if readcfg: ret, stream = self.readConfig() readcfg = False - self.dumpConfig(stream) + if stream is not None: + self.dumpConfig(stream, self._conf["dump"] == 2) + else: # pragma: no cover + output("ERROR: The configuration stream failed because of the invalid syntax.") if not self._conf.get("test", False): return ret @@ -275,7 +280,8 @@ class Fail2banCmdLine(): self.configurator.readAll() ret = self.configurator.getOptions(jail, self._conf, ignoreWrong=not self.cleanConfOnly) - self.configurator.convertToProtocol() + self.configurator.convertToProtocol( + allow_no_files=self._conf.get("dump", False)) stream = self.configurator.getConfigStream() except Exception as e: logSys.error("Failed during configuration: %s" % e) @@ -283,9 +289,15 @@ class Fail2banCmdLine(): return ret, stream @staticmethod - def dumpConfig(cmd): + def dumpConfig(cmd, pretty=False): + if pretty: + from pprint import pformat + def _output(s): + output(pformat(s, width=1000, indent=2)) + else: + _output = output for c in cmd: - output(c) + _output(c) return True # diff --git a/fail2ban/client/jailreader.py b/fail2ban/client/jailreader.py index ce0ed3b6..ffc28752 100644 --- a/fail2ban/client/jailreader.py +++ b/fail2ban/client/jailreader.py @@ -235,9 +235,12 @@ class JailReader(ConfigReader): found_files += 1 stream.append( ["set", self.__name, "addlogpath", p, tail]) - if not (found_files or allow_no_files): - raise ValueError( - "Have not found any log file for %s jail" % self.__name) + if not found_files: + msg = "Have not found any log file for %s jail" % self.__name + if not allow_no_files: + raise ValueError(msg) + logSys.warning(msg) + elif opt == "logencoding": stream.append(["set", self.__name, "logencoding", value]) elif opt == "backend": diff --git a/fail2ban/tests/fail2banclienttestcase.py b/fail2ban/tests/fail2banclienttestcase.py index caacf63c..083ce815 100644 --- a/fail2ban/tests/fail2banclienttestcase.py +++ b/fail2ban/tests/fail2banclienttestcase.py @@ -426,7 +426,11 @@ class Fail2banClientTest(Fail2banClientServerBase): startparams = _start_params(tmp, True) self.execSuccess(startparams, "-vvd") self.assertLogged("Loading files") - self.assertLogged("logtarget") + self.assertLogged("['set', 'logtarget',") + self.pruneLog() + # pretty dump: + self.execSuccess(startparams, "--dp") + self.assertLogged("['set', 'logtarget',") @with_tmpdir @with_kill_srv From e20f6204d325273eadd724e8a15401379b634d5c Mon Sep 17 00:00:00 2001 From: sebres Date: Thu, 7 Sep 2017 19:09:18 +0200 Subject: [PATCH 2/3] don't put parameters starting with `known/` to the ready stream (intermediate options only), makes streams and dumps of configuration shorter and better readable --- fail2ban/client/actionreader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fail2ban/client/actionreader.py b/fail2ban/client/actionreader.py index 559705bc..d7bba61f 100644 --- a/fail2ban/client/actionreader.py +++ b/fail2ban/client/actionreader.py @@ -88,11 +88,11 @@ class ActionReader(DefinitionInitConfigReader): stream.append(head + ["addaction", self._name]) multi = [] for opt, optval in opts.iteritems(): - if opt in self._configOpts: + if opt in self._configOpts and not opt.startswith('known/'): multi.append([opt, optval]) if self._initOpts: for opt, optval in self._initOpts.iteritems(): - if opt not in self._configOpts: + if opt not in self._configOpts and not opt.startswith('known/'): multi.append([opt, optval]) if len(multi) > 1: stream.append(["multi-set", self._jailName, "action", self._name, multi]) From 462b534469dcdd852e6c551ad71ea0549e63ecc2 Mon Sep 17 00:00:00 2001 From: sebres Date: Thu, 7 Sep 2017 19:21:45 +0200 Subject: [PATCH 3/3] restrict saving of previous known values to section-related (don't overwrite with the values of other sections, especially like "INCLUDES", etc.) --- fail2ban/client/configparserinc.py | 9 ++++----- fail2ban/client/configreader.py | 6 +++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/fail2ban/client/configparserinc.py b/fail2ban/client/configparserinc.py index b626be9b..d220ed44 100644 --- a/fail2ban/client/configparserinc.py +++ b/fail2ban/client/configparserinc.py @@ -20,8 +20,8 @@ # Author: Yaroslav Halchenko # Modified: Cyril Jaquier -__author__ = 'Yaroslav Halhenko' -__copyright__ = 'Copyright (c) 2007 Yaroslav Halchenko' +__author__ = 'Yaroslav Halhenko, Serg G. Brester (aka sebres)' +__copyright__ = 'Copyright (c) 2007 Yaroslav Halchenko, 2015 Serg G. Brester (aka sebres)' __license__ = 'GPL' import os @@ -150,7 +150,7 @@ after = 1.conf if seclwr == 'known': # try get raw value from known options: try: - v = self._sections['KNOWN'][opt] + v = self._sections['KNOWN/'+section][opt] except KeyError: # fallback to default: try: @@ -297,7 +297,6 @@ after = 1.conf # merge defaults and all sections to self: alld.update(cfg.get_defaults()) for n, s in cfg.get_sections().iteritems(): - curalls = alls # conditional sections cond = SafeConfigParserWithIncludes.CONDITIONAL_RE.match(n) if cond: @@ -313,7 +312,7 @@ after = 1.conf s2 = alls.get(n) if isinstance(s2, dict): # save previous known values, for possible using in local interpolations later: - self.merge_section('KNOWN', s2, '') + self.merge_section('KNOWN/'+n, s2, '') # merge section s2.update(s) else: diff --git a/fail2ban/client/configreader.py b/fail2ban/client/configreader.py index 381af759..577a5a16 100644 --- a/fail2ban/client/configreader.py +++ b/fail2ban/client/configreader.py @@ -20,8 +20,8 @@ # Author: Cyril Jaquier # Modified by: Yaroslav Halchenko (SafeConfigParserWithIncludes) -__author__ = "Cyril Jaquier" -__copyright__ = "Copyright (c) 2004 Cyril Jaquier" +__author__ = "Cyril Jaquier, Yaroslav Halchenko, Serg G. Brester (aka sebres)" +__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2007 Yaroslav Halchenko, 2015 Serg G. Brester (aka sebres)" __license__ = "GPL" import glob @@ -110,7 +110,7 @@ class ConfigReader(): def sections(self): try: - return (n for n in self._cfg.sections() if n != 'KNOWN') + return (n for n in self._cfg.sections() if not n.startswith('KNOWN/')) except AttributeError: return []