From 9c7bd8080762d63610b8134f84163e45547616ba Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Feb 2020 20:09:13 +0100 Subject: [PATCH 1/3] fail2ban-regex: stop endless logging on closed streams (redirected pipes like `... | head -n 100`), exit if stdout channel is closed --- fail2ban/client/fail2banregex.py | 1 + fail2ban/helpers.py | 35 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/fail2ban/client/fail2banregex.py b/fail2ban/client/fail2banregex.py index 334c031f..513b765d 100644 --- a/fail2ban/client/fail2banregex.py +++ b/fail2ban/client/fail2banregex.py @@ -709,6 +709,7 @@ class Fail2banRegex(object): def exec_command_line(*args): + logging.exitOnIOError = True parser = get_opt_parser() (opts, args) = parser.parse_args(*args) errors = [] diff --git a/fail2ban/helpers.py b/fail2ban/helpers.py index 241543c1..6f2bcdd7 100644 --- a/fail2ban/helpers.py +++ b/fail2ban/helpers.py @@ -208,6 +208,25 @@ class FormatterWithTraceBack(logging.Formatter): return logging.Formatter.format(self, record) +logging.exitOnIOError = False +def __stopOnIOError(logSys=None, logHndlr=None): # pragma: no cover + if logSys and len(logSys.handlers): + logSys.removeHandler(logSys.handlers[0]) + if logHndlr: + logHndlr.close = lambda: None + logging.StreamHandler.flush = lambda self: None + #sys.excepthook = lambda *args: None + if logging.exitOnIOError: + try: + sys.stderr.close() + except: + pass + sys.exit(0) + +try: + BrokenPipeError +except NameError: # pragma: 3.x no cover + BrokenPipeError = IOError __origLog = logging.Logger._log def __safeLog(self, level, msg, args, **kwargs): """Safe log inject to avoid possible errors by unsafe log-handlers, @@ -223,6 +242,10 @@ def __safeLog(self, level, msg, args, **kwargs): try: # if isEnabledFor(level) already called... __origLog(self, level, msg, args, **kwargs) + except (BrokenPipeError, IOError) as e: # pragma: no cover + if e.errno == 32: # closed / broken pipe + __stopOnIOError(self) + raise except Exception as e: # pragma: no cover - unreachable if log-handler safe in this python-version try: for args in ( @@ -237,6 +260,18 @@ def __safeLog(self, level, msg, args, **kwargs): pass logging.Logger._log = __safeLog +__origLogFlush = logging.StreamHandler.flush +def __safeLogFlush(self): + """Safe flush inject stopping endless logging on closed streams (redirected pipe). + """ + try: + __origLogFlush(self) + except (BrokenPipeError, IOError) as e: # pragma: no cover + if e.errno == 32: # closed / broken pipe + __stopOnIOError(None, self) + raise +logging.StreamHandler.flush = __safeLogFlush + def getLogger(name): """Get logging.Logger instance with Fail2Ban logger name convention """ From 3f489070646b363aa0374681fe910f05521cd247 Mon Sep 17 00:00:00 2001 From: sebres Date: Fri, 7 Feb 2020 11:08:01 +0100 Subject: [PATCH 2/3] amend to f3dbc9dda10e52610e3de26f538b5581fd905505: change main thread-name back to `fail2ban-server`; implements new command line option `--pname` to specify it by start of server (default `fail2ban-server`); closes gh-2623 (revert change of main thread-name, because it can affect process-name too, so `pgrep` & co. may be confused) --- fail2ban/client/fail2bancmdline.py | 3 ++- fail2ban/server/server.py | 5 +++-- man/fail2ban-client.1 | 5 ++++- man/fail2ban-server.1 | 5 ++++- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/fail2ban/client/fail2bancmdline.py b/fail2ban/client/fail2bancmdline.py index 1268ee9f..3c6bd0bf 100644 --- a/fail2ban/client/fail2bancmdline.py +++ b/fail2ban/client/fail2bancmdline.py @@ -97,6 +97,7 @@ class Fail2banCmdLine(): output(" -c configuration directory") output(" -s socket path") output(" -p pidfile path") + output(" --pname name of the process (main thread) to identify instance (default fail2ban-server)") output(" --loglevel logging level") output(" --logtarget logging target, use file-name or stdout, stderr, syslog or sysout.") output(" --syslogsocket auto|") @@ -185,7 +186,7 @@ class Fail2banCmdLine(): try: cmdOpts = 'hc:s:p:xfbdtviqV' cmdLongOpts = ['loglevel=', 'logtarget=', 'syslogsocket=', 'test', 'async', - 'timeout=', 'str2sec=', 'help', 'version', 'dp', '--dump-pretty'] + 'pname=', 'timeout=', 'str2sec=', 'help', 'version', 'dp', '--dump-pretty'] optList, self._args = getopt.getopt(self._argv[1:], cmdOpts, cmdLongOpts) except getopt.GetoptError: self.dispUsage() diff --git a/fail2ban/server/server.py b/fail2ban/server/server.py index 7c820e49..22814280 100644 --- a/fail2ban/server/server.py +++ b/fail2ban/server/server.py @@ -80,8 +80,6 @@ class Server: 'Linux': '/dev/log', } self.__prev_signals = {} - # replace real thread name with short process name (for top/ps/pstree or diagnostic): - prctl_set_th_name('f2b/server') def __sigTERMhandler(self, signum, frame): # pragma: no cover - indirect tested logSys.debug("Caught signal %d. Exiting", signum) @@ -112,6 +110,9 @@ class Server: logSys.error(err) raise ServerInitializationError(err) # We are daemon. + + # replace main thread (and process) name to identify server (for top/ps/pstree or diagnostic): + prctl_set_th_name(conf.get("pname", "fail2ban-server")) # Set all logging parameters (or use default if not specified): self.__verbose = conf.get("verbose", None) diff --git a/man/fail2ban-client.1 b/man/fail2ban-client.1 index 9ea61084..32a90851 100644 --- a/man/fail2ban-client.1 +++ b/man/fail2ban-client.1 @@ -1,5 +1,5 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH FAIL2BAN-CLIENT "1" "January 2020" "fail2ban-client v0.10.5" "User Commands" +.TH FAIL2BAN-CLIENT "1" "February 2020" "fail2ban-client v0.10.5" "User Commands" .SH NAME fail2ban-client \- configure and control the server .SH SYNOPSIS @@ -19,6 +19,9 @@ socket path \fB\-p\fR pidfile path .TP +\fB\-\-pname\fR +name of the process (main thread) to identify instance (default fail2ban\-server) +.TP \fB\-\-loglevel\fR logging level .TP diff --git a/man/fail2ban-server.1 b/man/fail2ban-server.1 index ddf9b303..d75158a8 100644 --- a/man/fail2ban-server.1 +++ b/man/fail2ban-server.1 @@ -1,5 +1,5 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. -.TH FAIL2BAN-SERVER "1" "January 2020" "fail2ban-server v0.10.5" "User Commands" +.TH FAIL2BAN-SERVER "1" "February 2020" "fail2ban-server v0.10.5" "User Commands" .SH NAME fail2ban-server \- start the server .SH SYNOPSIS @@ -19,6 +19,9 @@ socket path \fB\-p\fR pidfile path .TP +\fB\-\-pname\fR +name of the process (main thread) to identify instance (default fail2ban\-server) +.TP \fB\-\-loglevel\fR logging level .TP From 7a28861fc709d488c59a28ecf58e4ef5e5b79f4d Mon Sep 17 00:00:00 2001 From: sebres Date: Fri, 7 Feb 2020 13:52:45 +0100 Subject: [PATCH 3/3] review of command line: more long-named options can be supplied via command line --- fail2ban/client/fail2bancmdline.py | 33 ++++++++++++++++-------------- man/fail2ban-client.1 | 6 +++--- man/fail2ban-server.1 | 6 +++--- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/fail2ban/client/fail2bancmdline.py b/fail2ban/client/fail2bancmdline.py index 3c6bd0bf..53c86de6 100644 --- a/fail2ban/client/fail2bancmdline.py +++ b/fail2ban/client/fail2bancmdline.py @@ -35,7 +35,8 @@ logSys = getLogger("fail2ban") def output(s): # pragma: no cover print(s) -CONFIG_PARAMS = ("socket", "pidfile", "logtarget", "loglevel", "syslogsocket",) +# Config parameters required to start fail2ban which can be also set via command line (overwrite fail2ban.conf), +CONFIG_PARAMS = ("socket", "pidfile", "logtarget", "loglevel", "syslogsocket") # Used to signal - we are in test cases (ex: prevents change logging params, log capturing, etc) PRODUCTION = True @@ -94,9 +95,9 @@ class Fail2banCmdLine(): output("and bans the corresponding IP addresses using firewall rules.") output("") output("Options:") - output(" -c configuration directory") - output(" -s socket path") - output(" -p pidfile path") + output(" -c, --conf configuration directory") + output(" -s, --socket socket path") + output(" -p, --pidfile pidfile path") output(" --pname name of the process (main thread) to identify instance (default fail2ban-server)") output(" --loglevel logging level") output(" --logtarget logging target, use file-name or stdout, stderr, syslog or sysout.") @@ -130,17 +131,15 @@ class Fail2banCmdLine(): """ for opt in optList: o = opt[0] - if o == "-c": + if o in ("-c", "--conf"): self._conf["conf"] = opt[1] - elif o == "-s": + elif o in ("-s", "--socket"): self._conf["socket"] = opt[1] - elif o == "-p": + elif o in ("-p", "--pidfile"): self._conf["pidfile"] = opt[1] - elif o.startswith("--log") or o.startswith("--sys"): - self._conf[ o[2:] ] = opt[1] - elif o in ["-d", "--dp", "--dump-pretty"]: + elif o in ("-d", "--dp", "--dump-pretty"): self._conf["dump"] = True if o == "-d" else 2 - elif o == "-t" or o == "--test": + elif o in ("-t", "--test"): self.cleanConfOnly = True self._conf["test"] = True elif o == "-v": @@ -164,12 +163,14 @@ class Fail2banCmdLine(): from ..server.mytime import MyTime output(MyTime.str2seconds(opt[1])) return True - elif o in ["-h", "--help"]: + elif o in ("-h", "--help"): self.dispUsage() return True - elif o in ["-V", "--version"]: + elif o in ("-V", "--version"): self.dispVersion(o == "-V") return True + elif o.startswith("--"): # other long named params (see also resetConf) + self._conf[ o[2:] ] = opt[1] return None def initCmdLine(self, argv): @@ -186,7 +187,8 @@ class Fail2banCmdLine(): try: cmdOpts = 'hc:s:p:xfbdtviqV' cmdLongOpts = ['loglevel=', 'logtarget=', 'syslogsocket=', 'test', 'async', - 'pname=', 'timeout=', 'str2sec=', 'help', 'version', 'dp', '--dump-pretty'] + 'conf=', 'pidfile=', 'pname=', 'socket=', + 'timeout=', 'str2sec=', 'help', 'version', 'dp', '--dump-pretty'] optList, self._args = getopt.getopt(self._argv[1:], cmdOpts, cmdLongOpts) except getopt.GetoptError: self.dispUsage() @@ -228,7 +230,8 @@ class Fail2banCmdLine(): if not conf: self.configurator.readEarly() conf = self.configurator.getEarlyOptions() - self._conf[o] = conf[o] + if o in conf: + self._conf[o] = conf[o] logSys.info("Using socket file %s", self._conf["socket"]) diff --git a/man/fail2ban-client.1 b/man/fail2ban-client.1 index 32a90851..ad4fa0ed 100644 --- a/man/fail2ban-client.1 +++ b/man/fail2ban-client.1 @@ -10,13 +10,13 @@ Fail2Ban v0.10.5 reads log file that contains password failure report and bans the corresponding IP addresses using firewall rules. .SH OPTIONS .TP -\fB\-c\fR +\fB\-c\fR, \fB\-\-conf\fR configuration directory .TP -\fB\-s\fR +\fB\-s\fR, \fB\-\-socket\fR socket path .TP -\fB\-p\fR +\fB\-p\fR, \fB\-\-pidfile\fR pidfile path .TP \fB\-\-pname\fR diff --git a/man/fail2ban-server.1 b/man/fail2ban-server.1 index d75158a8..c7516cc8 100644 --- a/man/fail2ban-server.1 +++ b/man/fail2ban-server.1 @@ -10,13 +10,13 @@ Fail2Ban v0.10.5 reads log file that contains password failure report and bans the corresponding IP addresses using firewall rules. .SH OPTIONS .TP -\fB\-c\fR +\fB\-c\fR, \fB\-\-conf\fR configuration directory .TP -\fB\-s\fR +\fB\-s\fR, \fB\-\-socket\fR socket path .TP -\fB\-p\fR +\fB\-p\fR, \fB\-\-pidfile\fR pidfile path .TP \fB\-\-pname\fR