diff --git a/fail2ban/server/asyncserver.py b/fail2ban/server/asyncserver.py index 9cc74658..ea6cc326 100644 --- a/fail2ban/server/asyncserver.py +++ b/fail2ban/server/asyncserver.py @@ -80,7 +80,10 @@ class RequestHandler(asynchat.async_chat): # Deserialize message = loads(message) # Gives the message to the transmitter. - message = self.__transmitter.proceed(message) + if self.__transmitter: + message = self.__transmitter.proceed(message) + else: + message = ['SHUTDOWN'] # Serializes the response. message = dumps(message, HIGHEST_PROTOCOL) # Sends the response to the client. @@ -228,6 +231,13 @@ class AsyncServer(asyncore.dispatcher): ## # Stops the communication server. + def stop_communication(self): + logSys.debug("Stop communication") + self.__transmitter = None + + ## + # Stops the server. + def stop(self): self.close() diff --git a/fail2ban/server/database.py b/fail2ban/server/database.py index e37d8765..4b4a3ea9 100644 --- a/fail2ban/server/database.py +++ b/fail2ban/server/database.py @@ -643,7 +643,6 @@ class Fail2BanDb(object): cur = self._db.cursor() return cur.execute(query, queryArgs) - @commitandrollback def _getCurrentBans(self, cur, jail = None, ip = None, forbantime=None, fromtime=None): if fromtime is None: fromtime = MyTime.time() @@ -666,20 +665,29 @@ class Fail2BanDb(object): cur = self._db.cursor() return cur.execute(query, queryArgs) - def getCurrentBans(self, jail = None, ip = None, forbantime=None, fromtime=None): + @commitandrollback + def getCurrentBans(self, cur, jail = None, ip = None, forbantime=None, fromtime=None): tickets = [] ticket = None - results = list(self._getCurrentBans(jail=jail, ip=ip, forbantime=forbantime, fromtime=fromtime)) - - if results: - for banip, timeofban, bantime, bancount, data in results: - # logSys.debug('restore ticket %r, %r, %r', banip, timeofban, data) - ticket = FailTicket(banip, timeofban, data=data) - # logSys.debug('restored ticket: %r', ticket) - ticket.setBanTime(bantime) - ticket.setBanCount(bancount) - tickets.append(ticket) + for ticket in self._getCurrentBans(cur, jail=jail, ip=ip, + forbantime=forbantime, fromtime=fromtime + ): + # can produce unpack error (database may return sporadical wrong-empty row): + try: + banip, timeofban, bantime, bancount, data = ticket + # additionally check for empty values: + if banip is None or banip == "": # pragma: no cover + raise ValueError('unexpected value %r' % (banip,)) + except ValueError as e: # pragma: no cover + logSys.debug("get current bans: ignore row %r - %s", ticket, e) + continue + # logSys.debug('restore ticket %r, %r, %r', banip, timeofban, data) + ticket = FailTicket(banip, timeofban, data=data) + # logSys.debug('restored ticket: %r', ticket) + ticket.setBanTime(bantime) + ticket.setBanCount(bancount) + tickets.append(ticket) return tickets if ip is None else ticket diff --git a/fail2ban/server/jail.py b/fail2ban/server/jail.py index 2f31fc4b..33f6db0b 100644 --- a/fail2ban/server/jail.py +++ b/fail2ban/server/jail.py @@ -274,8 +274,9 @@ class Jail(object): if not self.getBanTimeExtra('increment'): forbantime = self.actions.getBanTime() for ticket in self.database.getCurrentBans(jail=self, forbantime=forbantime): - #logSys.debug('restored ticket: %s', ticket) - if not self.filter.inIgnoreIPList(ticket.getIP(), log_ignore=True): + try: + #logSys.debug('restored ticket: %s', ticket) + if self.filter.inIgnoreIPList(ticket.getIP(), log_ignore=True): continue # mark ticked was restored from database - does not put it again into db: ticket.restored = True # correct start time / ban time (by the same end of ban): @@ -287,8 +288,12 @@ class Jail(object): if btm != -1 and btm <= 0: continue self.putFailTicket(ticket) + except Exception as e: # pragma: no cover + logSys.error('Restore ticket failed: %s', e, + exc_info=logSys.getEffectiveLevel()<=logging.DEBUG) except Exception as e: # pragma: no cover - logSys.error('%s', e, exc_info=logSys.getEffectiveLevel()<=logging.DEBUG) + logSys.error('Restore bans failed: %s', e, + exc_info=logSys.getEffectiveLevel()<=logging.DEBUG) def start(self): """Start the jail, by starting filter and actions threads. diff --git a/fail2ban/server/server.py b/fail2ban/server/server.py index 3654ea6e..494fd356 100644 --- a/fail2ban/server/server.py +++ b/fail2ban/server/server.py @@ -159,17 +159,16 @@ class Server: 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) os.remove(pidfile) 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() - Observers.Main = None - logSys.info("Exiting Fail2ban") + + # Stop (if not yet already executed): + self.quit() def quit(self): # Give observer a small chance to complete its work before exit @@ -183,8 +182,7 @@ class Server: # are exiting) # See https://github.com/fail2ban/fail2ban/issues/7 if self.__asyncServer is not None: - self.__asyncServer.stop() - self.__asyncServer = None + self.__asyncServer.stop_communication() # Now stop all the jails self.stopAllJail() @@ -205,6 +203,16 @@ class Server: for s, sh in self.__prev_signals.iteritems(): signal.signal(s, sh) + # Stop observer and exit + if Observers.Main is not None: + Observers.Main.stop() + Observers.Main = None + # Stop async + if self.__asyncServer is not None: + self.__asyncServer.stop() + self.__asyncServer = None + logSys.info("Exiting Fail2ban") + # Prevent to call quit twice: self.quit = lambda: False