diff --git a/fail2ban/client/fail2banclient.py b/fail2ban/client/fail2banclient.py index 331c5f34..007afd57 100755 --- a/fail2ban/client/fail2banclient.py +++ b/fail2ban/client/fail2banclient.py @@ -310,7 +310,7 @@ class Fail2banClient(Fail2banCmdLine, Thread): # stop options - jail name or --all break if self.__ping(timeout=-1): - if len(cmd) == 1: + if len(cmd) == 1 or cmd[1] == '--all': jail = '--all' ret, stream = self.readConfig() else: diff --git a/fail2ban/client/fail2banserver.py b/fail2ban/client/fail2banserver.py index aba8f8ca..dfee34d2 100644 --- a/fail2ban/client/fail2banserver.py +++ b/fail2ban/client/fail2banserver.py @@ -184,13 +184,19 @@ class Fail2banServer(Fail2banCmdLine): logSys.log(5, ' server phase %s', phase) if not phase.get('start', False): raise ServerExecutionException('Async configuration of server failed') + # event for server ready flag: + def _server_ready(): + phase['start-ready'] = True + logSys.log(5, ' server phase %s', phase) + # notify waiting thread if server really ready + self._conf['onstart'] = _server_ready # Start server, daemonize it, etc. pid = os.getpid() server = Fail2banServer.startServerDirect(self._conf, background) + # notify waiting thread server ready resp. done (background execution, error case, etc): if not async: - phase['start-ready'] = True - logSys.log(5, ' server phase %s', phase) + _server_ready() # If forked - just exit other processes if pid != os.getpid(): # pragma: no cover os._exit(0) diff --git a/fail2ban/server/asyncserver.py b/fail2ban/server/asyncserver.py index a489b585..d1818d7a 100644 --- a/fail2ban/server/asyncserver.py +++ b/fail2ban/server/asyncserver.py @@ -147,6 +147,7 @@ class AsyncServer(asyncore.dispatcher): self.__sock = "/var/run/fail2ban/fail2ban.sock" self.__init = False self.__active = False + self.onstart = None ## # Returns False as we only read the socket first. @@ -196,6 +197,9 @@ class AsyncServer(asyncore.dispatcher): self.listen(1) # Sets the init flag. self.__init = self.__loop = self.__active = True + # Execute on start event (server ready): + if self.onstart: + self.onstart() # Event loop as long as active: loop(lambda: self.__loop, use_poll=use_poll) self.__active = False diff --git a/fail2ban/server/filterpoll.py b/fail2ban/server/filterpoll.py index 59310bed..e785a0e4 100644 --- a/fail2ban/server/filterpoll.py +++ b/fail2ban/server/filterpoll.py @@ -97,32 +97,40 @@ class FilterPoll(FileFilter): def run(self): while self.active: - if logSys.getEffectiveLevel() <= 6: - logSys.log(6, "Woke up idle=%s with %d files monitored", - self.idle, self.getLogCount()) - if self.idle: - if not Utils.wait_for(lambda: not self.active or not self.idle, - self.sleeptime * 10, self.sleeptime - ): - self.ticks += 1 - continue - # Get file modification - modlst = [] - Utils.wait_for(lambda: not self.active or self.getModified(modlst), - self.sleeptime) - for filename in modlst: - self.getFailures(filename) - self.__modified = True + try: + if logSys.getEffectiveLevel() <= 6: + logSys.log(6, "Woke up idle=%s with %d files monitored", + self.idle, self.getLogCount()) + if self.idle: + if not Utils.wait_for(lambda: not self.active or not self.idle, + self.sleeptime * 10, self.sleeptime + ): + self.ticks += 1 + continue + # Get file modification + modlst = [] + Utils.wait_for(lambda: not self.active or self.getModified(modlst), + self.sleeptime) + for filename in modlst: + self.getFailures(filename) + self.__modified = True - self.ticks += 1 - if self.__modified: - try: - while True: - ticket = self.failManager.toBan() - self.jail.putFailTicket(ticket) - except FailManagerEmpty: - self.failManager.cleanup(MyTime.time()) - self.__modified = False + self.ticks += 1 + if self.__modified: + try: + while True: + ticket = self.failManager.toBan() + self.jail.putFailTicket(ticket) + except FailManagerEmpty: + self.failManager.cleanup(MyTime.time()) + self.__modified = False + except Exception as e: # pragma: no cover + if not self.active: # if not active - error by stop... + break + logSys.error("Caught unhandled exception in main cycle: %r", e, + exc_info=logSys.getEffectiveLevel()<=logging.DEBUG) + # incr common error counter: + self.commonError() logSys.debug("[%s] filter terminated", self.jailName) return True @@ -151,9 +159,9 @@ class FilterPoll(FileFilter): return True except Exception as e: # stil alive (may be deleted because multi-threaded): - if not self.getLog(filename): + if not self.getLog(filename) or self.__prevStats.get(filename) is None: logSys.warning("Log %r seems to be down: %s", filename, e) - return + return False # log error: if self.__file404Cnt[filename] < 2: logSys.error("Unable to get stat on %s because of: %s", diff --git a/fail2ban/server/server.py b/fail2ban/server/server.py index 1ede7b84..4f11a7e3 100644 --- a/fail2ban/server/server.py +++ b/fail2ban/server/server.py @@ -82,12 +82,12 @@ class Server: } self.__prev_signals = {} - def __sigTERMhandler(self, signum, frame): - logSys.debug("Caught signal %d. Exiting" % signum) + def __sigTERMhandler(self, signum, frame): # pragma: no cover - indirect tested + logSys.debug("Caught signal %d. Exiting", signum) self.quit() - def __sigUSR1handler(self, signum, fname): - logSys.debug("Caught signal %d. Flushing logs" % signum) + def __sigUSR1handler(self, signum, fname): # pragma: no cover - indirect tested + logSys.debug("Caught signal %d. Flushing logs", signum) self.flushLogs() def _rebindSignal(self, s, new): @@ -138,12 +138,12 @@ class Server: # Creates a PID file. try: - logSys.debug("Creating PID file %s" % pidfile) + logSys.debug("Creating PID file %s", pidfile) pidFile = open(pidfile, 'w') pidFile.write("%s\n" % os.getpid()) pidFile.close() - except IOError as e: - logSys.error("Unable to create PID file: %s" % e) + except (OSError, IOError) as e: # pragma: no cover + logSys.error("Unable to create PID file: %s", e) # Create observers and start it: if observer: @@ -155,15 +155,16 @@ class Server: logSys.debug("Starting communication") try: self.__asyncServer = AsyncServer(self.__transm) + self.__asyncServer.onstart = conf.get('onstart') self.__asyncServer.start(sock, force) except AsyncServerException as e: logSys.error("Could not start server: %s", e) # Removes the PID file. try: - logSys.debug("Remove PID file %s" % pidfile) + logSys.debug("Remove PID file %s", pidfile) os.remove(pidfile) - except OSError as e: - logSys.error("Unable to remove PID file: %s" % e) + except (OSError, IOError) as e: # pragma: no cover + logSys.error("Unable to remove PID file: %s", e) # Stop observer and exit if Observers.Main is not None: Observers.Main.stop() @@ -260,7 +261,7 @@ class Server: def reloadJails(self, name, opts, begin): if begin: # begin reload: - if self.__reload_state and (name == '--all' or self.__reload_state.get(name)): + if self.__reload_state and (name == '--all' or self.__reload_state.get(name)): # pragma: no cover raise ValueError('Reload already in progress') logSys.info("Reload " + (("jail %s" % name) if name != '--all' else "all jails")) with self.__lock: @@ -391,11 +392,8 @@ class Server: def addFailRegex(self, name, value, multiple=False): flt = self.__jails[name].filter - if multiple: - for value in value: - logSys.debug(" failregex: %r", value) - flt.addFailRegex(value) - else: + if not multiple: value = (value,) + for value in value: logSys.debug(" failregex: %r", value) flt.addFailRegex(value) @@ -407,11 +405,8 @@ class Server: def addIgnoreRegex(self, name, value, multiple=False): flt = self.__jails[name].filter - if multiple: - for value in value: - logSys.debug(" ignoreregex: %r", value) - flt.addIgnoreRegex(value) - else: + if not multiple: value = (value,) + for value in value: logSys.debug(" ignoreregex: %r", value) flt.addIgnoreRegex(value) @@ -615,6 +610,7 @@ class Server: if logger.getEffectiveLevel() <= logging.DEBUG: # pragma: no cover if self.__verbose is None: self.__verbose = logging.DEBUG - logger.getEffectiveLevel() + 1 + if self.__verbose is not None and self.__verbose > 2: # pragma: no cover fmt = getVerbosityFormat(self.__verbose-1) # tell the handler to use this format hdlr.setFormatter(logging.Formatter(fmt)) @@ -686,7 +682,7 @@ class Server: if Fail2BanDb is not None: self.__db = Fail2BanDb(filename) self.__db.delAllJails() - else: + else: # pragma: no cover logSys.error( "Unable to import fail2ban database module as sqlite " "is not available.") diff --git a/fail2ban/tests/config/filter.d/zzz-generic-example.conf b/fail2ban/tests/config/filter.d/zzz-generic-example.conf index e2ae91b0..df30d725 100644 --- a/fail2ban/tests/config/filter.d/zzz-generic-example.conf +++ b/fail2ban/tests/config/filter.d/zzz-generic-example.conf @@ -15,4 +15,8 @@ before = common.conf _daemon = test-demo failregex = ^%(__prefix_line)sF2B: failure from $ -ignoreregex = + ^%(__prefix_line)sF2B: error from $ + +# just to test multiple ignoreregex: +ignoreregex = ^%(__prefix_line)sF2B: error from 192.0.2.251$ + ^%(__prefix_line)sF2B: error from 192.0.2.252$ diff --git a/fail2ban/tests/fail2banclienttestcase.py b/fail2ban/tests/fail2banclienttestcase.py index d8fc156b..e212ff23 100644 --- a/fail2ban/tests/fail2banclienttestcase.py +++ b/fail2ban/tests/fail2banclienttestcase.py @@ -735,7 +735,7 @@ class Fail2banServerTest(Fail2banClientServerBase): if unittest.F2B.log_level <= logging.DEBUG: # pragma: no cover _out_file(fn) - def _write_jail_cfg(enabled=(1, 2), actions=()): + def _write_jail_cfg(enabled=(1, 2), actions=(), backend="polling"): _write_file(pjoin(cfg, "jail.conf"), "w", "[INCLUDES]", "", "[DEFAULT]", "", @@ -744,7 +744,7 @@ class Fail2banServerTest(Fail2banClientServerBase): "findtime = 10m", "failregex = ^\s*failure (401|403) from ", "", - "[test-jail1]", "backend = polling", "filter =", + "[test-jail1]", "backend = " + backend, "filter =", "action = ", " test-action1[name='%(__name__)s']" if 1 in actions else "", " test-action2[name='%(__name__)s']" if 2 in actions else "", @@ -755,7 +755,7 @@ class Fail2banServerTest(Fail2banClientServerBase): " ^\s*error (401|403) from " if 2 in enabled else "", "enabled = true" if 1 in enabled else "", "", - "[test-jail2]", "backend = polling", "filter =", + "[test-jail2]", "backend = " + backend, "filter =", "action =", "logpath = " + test2log, "enabled = true" if 2 in enabled else "", @@ -999,6 +999,20 @@ class Fail2banServerTest(Fail2banClientServerBase): "[test-jail1] Ban 192.0.2.4", all=True ) + # backend-switch (restart instead of reload): + self.pruneLog("[test-phase 8a]") + _write_jail_cfg(enabled=[1], backend="xxx-unknown-backend-zzz") + self.execFailed(startparams, "reload") + self.assertLogged("Reload finished.", all=True, wait=MID_WAITTIME) + self.assertLogged( + "Restart jail 'test-jail1' (reason: 'polling' != ", + "Unknown backend ", all=True) + + self.pruneLog("[test-phase 8b]") + _write_jail_cfg(enabled=[1]) + self.execSuccess(startparams, "reload") + self.assertLogged("Reload finished.", all=True, wait=MID_WAITTIME) + # several small cases (cover several parts): self.pruneLog("[test-phase end-1]") # wrong jail (not-started): @@ -1012,4 +1026,11 @@ class Fail2banServerTest(Fail2banClientServerBase): self.assertNotLogged( "Creating new jail 'test-jail2'", "Jail 'test-jail2' started", all=True) - self.pruneLog() + + # restart all jails (without restart server): + self.pruneLog("[test-phase end-2]") + self.execSuccess(startparams, + "--async", "reload", "--restart", "--all") + self.assertLogged( + "Jail 'test-jail1' stopped", + "Jail 'test-jail1' started", all=True) diff --git a/fail2ban/tests/files/logs/zzz-generic-example b/fail2ban/tests/files/logs/zzz-generic-example index 2a4fb789..2044c387 100644 --- a/fail2ban/tests/files/logs/zzz-generic-example +++ b/fail2ban/tests/files/logs/zzz-generic-example @@ -45,3 +45,10 @@ Jun 22 20:37:04 server test-demo[402]: writeToStorage plist={ # -- wrong time direct in journal-line (used last known date): # failJSON: { "time": "2005-06-22T20:37:04", "match": true , "host": "192.0.2.2" } 0000-12-30 00:00:00 server test-demo[47831]: F2B: failure from 192.0.2.2 + +# failJSON: { "time": "2005-06-21T16:56:02", "match": true , "host": "192.0.2.250" } +[Jun 21 16:56:02] machine test-demo(pam_unix)[13709] F2B: error from 192.0.2.250 +# failJSON: { "match": false, "desc": "test 1st ignoreregex" } +[Jun 21 16:56:03] machine test-demo(pam_unix)[13709] F2B: error from 192.0.2.251 +# failJSON: { "match": false, "desc": "test 2nd ignoreregex" } +[Jun 21 16:56:04] machine test-demo(pam_unix)[13709] F2B: error from 192.0.2.252