temp commit: reload not ready...

pull/1557/head
sebres 2016-09-06 23:54:19 +02:00
parent f512628af2
commit b12a3acb06
14 changed files with 646 additions and 171 deletions

View File

@ -46,7 +46,7 @@ class Beautifier:
return self.__inputCmd return self.__inputCmd
def beautify(self, response): def beautify(self, response):
logSys.debug( logSys.log(5,
"Beautify " + repr(response) + " with " + repr(self.__inputCmd)) "Beautify " + repr(response) + " with " + repr(self.__inputCmd))
inC = self.__inputCmd inC = self.__inputCmd
msg = response msg = response

View File

@ -91,7 +91,7 @@ class Fail2banClient(Fail2banCmdLine, Thread):
client = CSocket(self._conf["socket"]) client = CSocket(self._conf["socket"])
ret = client.send(c) ret = client.send(c)
if ret[0] == 0: if ret[0] == 0:
logSys.debug("OK : %r", ret[1]) logSys.log(5, "OK : %r", ret[1])
if showRet or c[0] == 'echo': if showRet or c[0] == 'echo':
output(beautifier.beautify(ret[1])) output(beautifier.beautify(ret[1]))
else: else:
@ -104,7 +104,7 @@ class Fail2banClient(Fail2banCmdLine, Thread):
if showRet or c != ["ping"]: if showRet or c != ["ping"]:
self.__logSocketError() self.__logSocketError()
else: else:
logSys.debug(" -- ping failed -- %r", e) logSys.log(5, " -- ping failed -- %r", e)
return False return False
except Exception as e: # pragma: no cover except Exception as e: # pragma: no cover
if showRet or self._conf["verbose"] > 1: if showRet or self._conf["verbose"] > 1:
@ -226,11 +226,11 @@ class Fail2banClient(Fail2banCmdLine, Thread):
# prepare: read config, check configuration is valid, etc.: # prepare: read config, check configuration is valid, etc.:
if phase is not None: if phase is not None:
phase['start'] = True phase['start'] = True
logSys.debug(' client phase %s', phase) logSys.log(5, ' client phase %s', phase)
stream = self.__prepareStartServer() stream = self.__prepareStartServer()
if phase is not None: if phase is not None:
phase['ready'] = phase['start'] = (True if stream else False) phase['ready'] = phase['start'] = (True if stream else False)
logSys.debug(' client phase %s', phase) logSys.log(5, ' client phase %s', phase)
if not stream: if not stream:
return False return False
# configure server with config stream: # configure server with config stream:
@ -246,6 +246,10 @@ class Fail2banClient(Fail2banCmdLine, Thread):
# @param cmd the command line # @param cmd the command line
def __processCommand(self, cmd): def __processCommand(self, cmd):
# wrap tuple to list (because could be modified here):
if not isinstance(cmd, list):
cmd = list(cmd)
# process:
if len(cmd) == 1 and cmd[0] == "start": if len(cmd) == 1 and cmd[0] == "start":
ret = self.__startServer(self._conf["background"]) ret = self.__startServer(self._conf["background"])
@ -253,8 +257,12 @@ class Fail2banClient(Fail2banCmdLine, Thread):
return False return False
return ret return ret
elif len(cmd) == 1 and cmd[0] == "restart": elif len(cmd) >= 1 and cmd[0] == "restart":
# if restart jail - re-operate via "reload --restart ...":
if len(cmd) > 1:
cmd[0:1] = ["reload", "--restart"]
return self.__processCommand(cmd)
# restart server:
if self._conf.get("interactive", False): if self._conf.get("interactive", False):
output(' ## stop ... ') output(' ## stop ... ')
self.__processCommand(['stop']) self.__processCommand(['stop'])
@ -273,9 +281,21 @@ class Fail2banClient(Fail2banCmdLine, Thread):
return self.__processCommand(['start']) return self.__processCommand(['start'])
elif len(cmd) >= 1 and cmd[0] == "reload": elif len(cmd) >= 1 and cmd[0] == "reload":
# reload options:
opts = []
while len(cmd) >= 2:
if cmd[1] in ('--restart', "--unban", "--if-exists"):
opts.append(cmd[1])
del cmd[1]
else:
if len(cmd) > 2:
logSys.error("Unexpected argument(s) for reload: %r", cmd[1:])
return False
# stop options - jail name or --all
break
if self.__ping(): if self.__ping():
if len(cmd) == 1: if len(cmd) == 1:
jail = 'all' jail = '--all'
ret, stream = self.readConfig() ret, stream = self.readConfig()
else: else:
jail = cmd[1] jail = cmd[1]
@ -283,9 +303,10 @@ class Fail2banClient(Fail2banCmdLine, Thread):
# Do not continue if configuration is not 100% valid # Do not continue if configuration is not 100% valid
if not ret: if not ret:
return False return False
self.__processCmd([['stop', jail]], False) if self._conf.get("interactive", False):
# Configure the server output(' ## reload ... ')
return self.__processCmd(stream, True) # Reconfigure the server
return self.__processCmd([['reload', jail, opts, stream]], True)
else: else:
logSys.error("Could not find server") logSys.error("Could not find server")
return False return False
@ -320,7 +341,7 @@ class Fail2banClient(Fail2banCmdLine, Thread):
maxtime = self._conf["timeout"] maxtime = self._conf["timeout"]
# Wait for the server to start (the server has 30 seconds to answer ping) # Wait for the server to start (the server has 30 seconds to answer ping)
starttime = time.time() starttime = time.time()
logSys.debug("__waitOnServer: %r", (alive, maxtime)) logSys.log(5, "__waitOnServer: %r", (alive, maxtime))
test = lambda: os.path.exists(self._conf["socket"]) and self.__ping() test = lambda: os.path.exists(self._conf["socket"]) and self.__ping()
with VisualWait(self._conf["verbose"]) as vis: with VisualWait(self._conf["verbose"]) as vis:
sltime = 0.0125 / 2 sltime = 0.0125 / 2

View File

@ -49,8 +49,11 @@ protocol = [
['', "BASIC", ""], ['', "BASIC", ""],
["start", "starts the server and the jails"], ["start", "starts the server and the jails"],
["restart", "restarts the server"], ["restart", "restarts the server"],
["reload", "reloads the configuration without restart"], ["restart [--unban] [--if-exists] <JAIL>", "restarts the jail <JAIL> (alias for 'reload --restart ... <JAIL>')"],
["reload <JAIL>", "reloads the jail <JAIL>"], ["reload [--restart] [--unban] [--all]", "reloads the configuration without restarting of the server, the option '--restart' activates completely restarting of affected jails, thereby unbans IP addresses (if option '--unban' specified)"],
["reload [--restart] [--unban] [--if-exists] <JAIL>", "reloads the jail <JAIL>, or restarts it (if option '--restart' specified)"],
["unban --all", "unbans all IP addresses (in all jails)"],
["unban <IP> ... <IP>", "unbans <IP> (in all jails)"],
["stop", "stops all jails and terminate the server"], ["stop", "stops all jails and terminate the server"],
["status", "gets the current status of the server"], ["status", "gets the current status of the server"],
["ping", "tests if the server is alive"], ["ping", "tests if the server is alive"],

View File

@ -180,7 +180,7 @@ class Actions(JailThread, Mapping):
def getBanTime(self): def getBanTime(self):
return self.__banManager.getBanTime() return self.__banManager.getBanTime()
def removeBannedIP(self, ip): def removeBannedIP(self, ip=None, db=True, ifexists=False):
"""Removes banned IP calling actions' unban method """Removes banned IP calling actions' unban method
Remove a banned IP now, rather than waiting for it to expire, Remove a banned IP now, rather than waiting for it to expire,
@ -188,16 +188,20 @@ class Actions(JailThread, Mapping):
Parameters Parameters
---------- ----------
ip : str or IPAddr ip : str or IPAddr or None
The IP address to unban The IP address to unban or all IPs if None
Raises Raises
------ ------
ValueError ValueError
If `ip` is not banned If `ip` is not banned
""" """
# Unban all?
if ip is None:
return self.__flushBan(db)
# Single IP:
# Always delete ip from database (also if currently not banned) # Always delete ip from database (also if currently not banned)
if self._jail.database is not None: if db and self._jail.database is not None:
self._jail.database.delBan(self._jail, ip) self._jail.database.delBan(self._jail, ip)
# Find the ticket with the IP. # Find the ticket with the IP.
ticket = self.__banManager.getTicketByIP(ip) ticket = self.__banManager.getTicketByIP(ip)
@ -205,7 +209,11 @@ class Actions(JailThread, Mapping):
# Unban the IP. # Unban the IP.
self.__unBan(ticket) self.__unBan(ticket)
else: else:
raise ValueError("IP %s is not banned" % ip) if ifexists:
return 0
raise ValueError("%s is not banned" % ip)
return 1
def run(self): def run(self):
"""Main loop for Threading. """Main loop for Threading.
@ -336,14 +344,21 @@ class Actions(JailThread, Mapping):
for ticket in self.__banManager.unBanList(MyTime.time()): for ticket in self.__banManager.unBanList(MyTime.time()):
self.__unBan(ticket) self.__unBan(ticket)
def __flushBan(self): def __flushBan(self, db=False):
"""Flush the ban list. """Flush the ban list.
Unban all IP address which are still in the banning list. Unban all IP address which are still in the banning list.
""" """
logSys.debug("Flush ban list") logSys.debug("Flush ban list")
for ticket in self.__banManager.flushBanList(): lst = self.__banManager.flushBanList()
for ticket in lst:
# delete ip from database also:
if db and self._jail.database is not None:
ip = str(ticket.getIP())
self._jail.database.delBan(self._jail, ip)
# unban ip:
self.__unBan(ticket) self.__unBan(ticket)
return len(lst)
def __unBan(self, ticket): def __unBan(self, ticket):
"""Unbans host corresponding to the ticket. """Unbans host corresponding to the ticket.

View File

@ -88,8 +88,9 @@ class Filter(JailThread):
self.__ignoreCommand = False self.__ignoreCommand = False
## Default or preferred encoding (to decode bytes from file or journal): ## Default or preferred encoding (to decode bytes from file or journal):
self.__encoding = locale.getpreferredencoding() self.__encoding = locale.getpreferredencoding()
## Error counter ## Error counter (protected, so can be used in filter implementations)
self.__errors = 0 ## if it reached 100 (at once), run-cycle will go idle
self._errors = 0
## Ticks counter ## Ticks counter
self.ticks = 0 self.ticks = 0
@ -100,6 +101,30 @@ class Filter(JailThread):
def __repr__(self): def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.jail) return "%s(%r)" % (self.__class__.__name__, self.jail)
def clearAllParams(self):
""" Clear all lists/dicts parameters (used by reloading)
"""
self.delFailRegex()
self.delIgnoreRegex()
self.delIgnoreIP()
def reload(self, begin=True):
""" Begin or end of reloading resp. refreshing of all parameters
"""
if begin:
self.clearAllParams()
if hasattr(self, 'getLogPaths'):
self._reload_logs = dict((k, 1) for k in self.getLogPaths())
else:
if hasattr(self, '_reload_logs'):
dellogs = dict()
# if it was not reloaded - remove obsolete log file:
for path in self._reload_logs:
self.delLogPath(path)
delattr(self, '_reload_logs')
## ##
# Add a regular expression which matches the failure. # Add a regular expression which matches the failure.
# #
@ -119,8 +144,13 @@ class Filter(JailThread):
logSys.error(e) logSys.error(e)
raise e raise e
def delFailRegex(self, index): def delFailRegex(self, index=None):
try: try:
# clear all:
if index is None:
del self.__failRegex[:]
return
# delete by index:
del self.__failRegex[index] del self.__failRegex[index]
except IndexError: except IndexError:
logSys.error("Cannot remove regular expression. Index %d is not " logSys.error("Cannot remove regular expression. Index %d is not "
@ -152,8 +182,13 @@ class Filter(JailThread):
logSys.error(e) logSys.error(e)
raise e raise e
def delIgnoreRegex(self, index): def delIgnoreRegex(self, index=None):
try: try:
# clear all:
if index is None:
del self.__ignoreRegex[:]
return
# delete by index:
del self.__ignoreRegex[index] del self.__ignoreRegex[index]
except IndexError: except IndexError:
logSys.error("Cannot remove regular expression. Index %d is not " logSys.error("Cannot remove regular expression. Index %d is not "
@ -203,7 +238,7 @@ class Filter(JailThread):
value = MyTime.str2seconds(value) value = MyTime.str2seconds(value)
self.__findTime = value self.__findTime = value
self.failManager.setMaxTime(value) self.failManager.setMaxTime(value)
logSys.info("Set findtime = %s" % value) logSys.info(" findtime: %s", value)
## ##
# Get the time needed to find a failure. # Get the time needed to find a failure.
@ -232,10 +267,10 @@ class Filter(JailThread):
template = DatePatternRegex(pattern) template = DatePatternRegex(pattern)
self.dateDetector = DateDetector() self.dateDetector = DateDetector()
self.dateDetector.appendTemplate(template) self.dateDetector.appendTemplate(template)
logSys.info("Date pattern set to `%r`: `%s`" % logSys.info(" date pattern `%r`: `%s`",
(pattern, template.name)) pattern, template.name)
logSys.debug("Date pattern regex for %r: %s" % logSys.debug(" date pattern regex for %r: %s",
(pattern, template.regex)) pattern, template.regex)
## ##
# Get the date detector pattern, or Default Detectors if not changed # Get the date detector pattern, or Default Detectors if not changed
@ -261,7 +296,7 @@ class Filter(JailThread):
def setMaxRetry(self, value): def setMaxRetry(self, value):
self.failManager.setMaxRetry(value) self.failManager.setMaxRetry(value)
logSys.info("Set maxRetry = %s" % value) logSys.info(" maxRetry: %s", value)
## ##
# Get the maximum retry value. # Get the maximum retry value.
@ -280,7 +315,7 @@ class Filter(JailThread):
if int(value) <= 0: if int(value) <= 0:
raise ValueError("maxlines must be integer greater than zero") raise ValueError("maxlines must be integer greater than zero")
self.__lineBufferSize = int(value) self.__lineBufferSize = int(value)
logSys.info("Set maxlines = %i" % self.__lineBufferSize) logSys.info(" maxLines: %i", self.__lineBufferSize)
## ##
# Get the maximum line buffer size. # Get the maximum line buffer size.
@ -300,7 +335,7 @@ class Filter(JailThread):
encoding = locale.getpreferredencoding() encoding = locale.getpreferredencoding()
codecs.lookup(encoding) # Raise LookupError if invalid codec codecs.lookup(encoding) # Raise LookupError if invalid codec
self.__encoding = encoding self.__encoding = encoding
logSys.info("Set jail log file encoding to %s" % encoding) logSys.info(" encoding: %s" % encoding)
return encoding return encoding
## ##
@ -375,11 +410,16 @@ class Filter(JailThread):
ip = IPAddr(ipstr) ip = IPAddr(ipstr)
# log and append to ignore list # log and append to ignore list
logSys.debug("Add %r to ignore list (%r)", ip, ipstr) logSys.debug(" Add %r to ignore list (%r)", ip, ipstr)
self.__ignoreIpList.append(ip) self.__ignoreIpList.append(ip)
def delIgnoreIP(self, ip): def delIgnoreIP(self, ip=None):
logSys.debug("Remove %r from ignore list", ip) # clear all:
if ip is None:
del self.__ignoreIpList[:]
return
# delete by ip:
logSys.debug(" Remove %r from ignore list", ip)
self.__ignoreIpList.remove(ip) self.__ignoreIpList.remove(ip)
def logIgnoreIp(self, ip, log_ignore, ignore_source="unknown source"): def logIgnoreIp(self, ip, log_ignore, ignore_source="unknown source"):
@ -483,18 +523,18 @@ class Filter(JailThread):
tick = FailTicket(ip, unixTime, lines, data=fail) tick = FailTicket(ip, unixTime, lines, data=fail)
self.failManager.addFailure(tick) self.failManager.addFailure(tick)
# reset (halve) error counter (successfully processed line): # reset (halve) error counter (successfully processed line):
if self.__errors: if self._errors:
self.__errors //= 2 self._errors //= 2
except Exception as e: except Exception as e:
logSys.error("Failed to process line: %r, caught exception: %r", line, e, logSys.error("Failed to process line: %r, caught exception: %r", line, e,
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG) exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
# incr error counter, stop processing (going idle) after 100th error : # incr error counter, stop processing (going idle) after 100th error :
self.__errors += 1 self._errors += 1
# sleep a little bit (to get around time-related errors): # sleep a little bit (to get around time-related errors):
time.sleep(self.sleeptime) time.sleep(self.sleeptime)
if self.__errors >= 100: if self._errors >= 100:
logSys.error("Too many errors at once (%s), going idle", self.__errors) logSys.error("Too many errors at once (%s), going idle", self._errors)
self.__errors //= 2 self._errors //= 2
self.idle = True self.idle = True
## ##
@ -657,7 +697,10 @@ class FileFilter(Filter):
def addLogPath(self, path, tail=False, autoSeek=True): def addLogPath(self, path, tail=False, autoSeek=True):
if path in self.__logs: if path in self.__logs:
logSys.error(path + " already exists") if hasattr(self, '_reload_logs') and path in self._reload_logs:
del self._reload_logs[path]
else:
logSys.error(path + " already exists")
else: else:
log = FileContainer(path, self.getLogEncoding(), tail) log = FileContainer(path, self.getLogEncoding(), tail)
db = self.jail.database db = self.jail.database
@ -666,7 +709,7 @@ class FileFilter(Filter):
if lastpos and not tail: if lastpos and not tail:
log.setPos(lastpos) log.setPos(lastpos)
self.__logs[path] = log self.__logs[path] = log
logSys.info("Added logfile = %s (pos = %s, hash = %s)" , path, log.getPos(), log.getHash()) logSys.info("Added logfile: %r (pos = %s, hash = %s)" , path, log.getPos(), log.getHash())
if autoSeek: if autoSeek:
# if default, seek to "current time" - "find time": # if default, seek to "current time" - "find time":
if isinstance(autoSeek, bool): if isinstance(autoSeek, bool):
@ -692,7 +735,7 @@ class FileFilter(Filter):
db = self.jail.database db = self.jail.database
if db is not None: if db is not None:
db.updateLog(self.jail, log) db.updateLog(self.jail, log)
logSys.info("Removed logfile = %s" % path) logSys.info("Removed logfile: %r" % path)
self._delLogPath(path) self._delLogPath(path)
return return
@ -1055,10 +1098,14 @@ _decode_line_warn = {}
class JournalFilter(Filter): # pragma: systemd no cover class JournalFilter(Filter): # pragma: systemd no cover
def clearAllParams(self):
super(JournalFilter, self).clearAllParams()
self.delJournalMatch()
def addJournalMatch(self, match): # pragma: no cover - Base class, not used def addJournalMatch(self, match): # pragma: no cover - Base class, not used
pass pass
def delJournalMatch(self, match): # pragma: no cover - Base class, not used def delJournalMatch(self, match=None): # pragma: no cover - Base class, not used
pass pass
def getJournalMatch(self, match): # pragma: no cover - Base class, not used def getJournalMatch(self, match): # pragma: no cover - Base class, not used

View File

@ -31,7 +31,7 @@ from .failmanager import FailManagerEmpty
from .filter import FileFilter from .filter import FileFilter
from .mytime import MyTime from .mytime import MyTime
from .utils import Utils from .utils import Utils
from ..helpers import getLogger from ..helpers import getLogger, logging
# Gets the instance of the logger. # Gets the instance of the logger.
@ -137,28 +137,34 @@ class FilterPoll(FileFilter):
try: try:
logStats = os.stat(filename) logStats = os.stat(filename)
stats = logStats.st_mtime, logStats.st_ino, logStats.st_size stats = logStats.st_mtime, logStats.st_ino, logStats.st_size
pstats = self.__prevStats.get(filename, ()) pstats = self.__prevStats.get(filename, (0))
self.__file404Cnt[filename] = 0
if logSys.getEffectiveLevel() <= 7: if logSys.getEffectiveLevel() <= 7:
# we do not want to waste time on strftime etc if not necessary # we do not want to waste time on strftime etc if not necessary
dt = logStats.st_mtime - pstats[0] dt = logStats.st_mtime - pstats[0]
logSys.log(7, "Checking %s for being modified. Previous/current stats: %s / %s. dt: %s", logSys.log(7, "Checking %s for being modified. Previous/current stats: %s / %s. dt: %s",
filename, pstats, stats, dt) filename, pstats, stats, dt)
# os.system("stat %s | grep Modify" % filename) # os.system("stat %s | grep Modify" % filename)
self.__file404Cnt[filename] = 0
if pstats == stats: if pstats == stats:
return False return False
logSys.debug("%s has been modified", filename) logSys.debug("%s has been modified", filename)
self.__prevStats[filename] = stats self.__prevStats[filename] = stats
return True return True
except OSError as e: except Exception as e:
logSys.error("Unable to get stat on %s because of: %s" # stil alive (may be deleted because multi-threaded):
% (filename, e)) if not self.getLog(filename):
logSys.warning("Log %r seems to be down: %s", filename, e)
return
# log error:
if self.__file404Cnt[filename] < 2:
logSys.error("Unable to get stat on %s because of: %s",
filename, e,
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
# increase file and common error counters:
self.__file404Cnt[filename] += 1 self.__file404Cnt[filename] += 1
if self.__file404Cnt[filename] > 2: self._errors += 1
logSys.warning("Too many errors. Setting the jail idle") if self.__file404Cnt[filename] > 50:
if self.jail is not None: logSys.warning("Too many errors. Remove file %r from monitoring process", filename)
self.jail.idle = True
else:
logSys.warning("No jail is assigned to %s" % self)
self.__file404Cnt[filename] = 0 self.__file404Cnt[filename] = 0
self.delLogPath(filename)
return False return False

View File

@ -154,12 +154,16 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
# #
# @param match journalctl syntax matches # @param match journalctl syntax matches
def delJournalMatch(self, match): def delJournalMatch(self, match=None):
if match in self.__matches: # clear all:
if match is None:
del self.__matches[:]
# delete by index:
elif match in self.__matches:
del self.__matches[self.__matches.index(match)] del self.__matches[self.__matches.index(match)]
self.resetJournalMatches()
else: else:
raise ValueError("Match not found") raise ValueError("Match not found")
self.resetJournalMatches()
logSys.info("Removed journal match for: %r" % " ".join(match)) logSys.info("Removed journal match for: %r" % " ".join(match))
## ##

View File

@ -79,6 +79,7 @@ class Jail(object):
logSys.info("Creating new jail '%s'" % self.name) logSys.info("Creating new jail '%s'" % self.name)
if backend is not None: if backend is not None:
self._setBackend(backend) self._setBackend(backend)
self.backend = backend
def __repr__(self): def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.name) return "%s(%r)" % (self.__class__.__name__, self.name)
@ -220,7 +221,7 @@ class Jail(object):
jail=self, bantime=self.actions.getBanTime()): jail=self, bantime=self.actions.getBanTime()):
if not self.filter.inIgnoreIPList(ticket.getIP(), log_ignore=True): if not self.filter.inIgnoreIPList(ticket.getIP(), log_ignore=True):
self.__queue.put(ticket) self.__queue.put(ticket)
logSys.info("Jail '%s' started" % self.name) logSys.info("Jail %r started", self.name)
def stop(self): def stop(self):
"""Stop the jail, by stopping filter and actions threads. """Stop the jail, by stopping filter and actions threads.

View File

@ -62,14 +62,15 @@ class Jails(Mapping):
DuplicateJailException DuplicateJailException
If jail name is already present. If jail name is already present.
""" """
try: with self.__lock:
self.__lock.acquire()
if name in self._jails: if name in self._jails:
raise DuplicateJailException(name) if noduplicates:
raise DuplicateJailException(name)
else: else:
self._jails[name] = Jail(name, backend, db) self._jails[name] = Jail(name, backend, db)
finally:
self.__lock.release() def exists(self, name):
return name in self._jails
def __getitem__(self, name): def __getitem__(self, name):
try: try:

View File

@ -67,6 +67,7 @@ class Server:
self.__db = None self.__db = None
self.__daemon = daemon self.__daemon = daemon
self.__transm = Transmitter(self) self.__transm = Transmitter(self)
self.__reload_state = {}
#self.__asyncServer = AsyncServer(self.__transm) #self.__asyncServer = AsyncServer(self.__transm)
self.__asyncServer = None self.__asyncServer = None
self.__logLevel = None self.__logLevel = None
@ -184,41 +185,105 @@ class Server:
self.quit = lambda: False self.quit = lambda: False
def addJail(self, name, backend): def addJail(self, name, backend):
self.__jails.add(name, backend, self.__db) addflg = True
if self.__reload_state.get(name) and self.__jails.exists(name):
jail = self.__jails[name]
# if backend switch - restart instead of reload:
if jail.backend == backend:
addflg = False
logSys.info("Reload jail %r", name)
# prevent to reload the same jail twice (temporary keep it in state, needed to commit reload):
self.__reload_state[name] = None
else:
logSys.info("Restart jail %r (reason: %r != %r)", name, jail.backend, backend)
self.delJail(name, stop=True)
# prevent to start the same jail twice (no reload more - restart):
del self.__reload_state[name]
if addflg:
self.__jails.add(name, backend, self.__db)
if self.__db is not None: if self.__db is not None:
self.__db.addJail(self.__jails[name]) self.__db.addJail(self.__jails[name])
def delJail(self, name): def delJail(self, name, stop=True):
jail = self.__jails[name]
if stop and jail.isAlive():
logSys.debug("Stopping jail %r" % name)
jail.stop()
if self.__db is not None: if self.__db is not None:
self.__db.delJail(self.__jails[name]) self.__db.delJail(jail)
del self.__jails[name] del self.__jails[name]
def startJail(self, name): def startJail(self, name):
try: with self.__lock:
self.__lock.acquire() jail = self.__jails[name]
if not self.__jails[name].isAlive(): if not jail.isAlive():
self.__jails[name].start() jail.start()
finally: elif name in self.__reload_state:
self.__lock.release() logSys.info("Jail %r reloaded", name)
del self.__reload_state[name]
if jail.idle:
jail.idle = False
def stopJail(self, name): def stopJail(self, name):
logSys.debug("Stopping jail %s" % name) with self.__lock:
try: self.delJail(name, stop=True)
self.__lock.acquire()
if self.__jails[name].isAlive():
self.__jails[name].stop()
self.delJail(name)
finally:
self.__lock.release()
def stopAllJail(self): def stopAllJail(self):
logSys.info("Stopping all jails") logSys.info("Stopping all jails")
try: with self.__lock:
self.__lock.acquire() for name in self.__jails.keys():
for jail in self.__jails.keys(): self.delJail(name, stop=True)
self.stopJail(jail)
finally: def reloadJails(self, name, opts, begin):
self.__lock.release() if begin:
# begin reload:
if self.__reload_state and (name == '--all' or self.__reload_state.get(name)):
raise ValueError('Reload already in progress')
logSys.info("Reload " + (("jail %s" % name) if name != '--all' else "all jails"))
with self.__lock:
# if single jail:
if name != '--all':
jail = None
# test jail exists (throws exception if not):
if "--if-exists" not in opts or self.__jails.exists(name):
jail = self.__jails[name]
if jail:
# first unban all ips (will be not restored after (re)start):
if "--unban" in opts:
self.setUnbanIP(name)
# stop if expected:
if "--restart" in opts:
self.stopJail(name)
else:
# first unban all ips (will be not restored after (re)start):
if "--unban" in opts:
self.setUnbanIP()
# stop if expected:
if "--restart" in opts:
self.stopAllJail()
# first set all affected jail(s) to idle and reset filter regex and other lists/dicts:
for jn, jail in self.__jails.iteritems():
if name == '--all' or jn == name:
jail.idle = True
self.__reload_state[jn] = jail
jail.filter.reload(begin=True)
pass
else:
# end reload, all affected (or new) jails have already all new parameters (via stream) and (re)started:
with self.__lock:
deljails = []
for jn, jail in self.__jails.iteritems():
# still in reload state:
if jn in self.__reload_state:
# remove jails that are not reloaded (untouched, so not in new configuration)
deljails.append(jn)
else:
# commit (reload was finished):
jail.filter.reload(begin=False)
for jn in deljails:
self.delJail(jn)
self.__reload_state = {}
logSys.info("Reload finished.")
def setIdleJail(self, name, value): def setIdleJail(self, name, value):
self.__jails[name].idle = value self.__jails[name].idle = value
@ -309,7 +374,7 @@ class Server:
logSys.debug(" failregex: %r", value) logSys.debug(" failregex: %r", value)
flt.addFailRegex(value) flt.addFailRegex(value)
def delFailRegex(self, name, index): def delFailRegex(self, name, index=None):
self.__jails[name].filter.delFailRegex(index) self.__jails[name].filter.delFailRegex(index)
def getFailRegex(self, name): def getFailRegex(self, name):
@ -368,8 +433,20 @@ class Server:
def setBanIP(self, name, value): def setBanIP(self, name, value):
return self.__jails[name].filter.addBannedIP(value) return self.__jails[name].filter.addBannedIP(value)
def setUnbanIP(self, name, value): def setUnbanIP(self, name=None, value=None):
self.__jails[name].actions.removeBannedIP(value) if name is not None:
# in all jails:
jails = [self.__jails[name]]
else:
# single jail:
jails = self.__jails.values()
# unban given or all (if value is None):
cnt = 0
for jail in jails:
cnt += jail.actions.removeBannedIP(value, ifexists=(name is None))
if value and not cnt:
logSys.info("%s is not banned", value)
return cnt
def getBanTime(self, name): def getBanTime(self, name):
return self.__jails[name].actions.getBanTime() return self.__jails[name].actions.getBanTime()
@ -420,7 +497,10 @@ class Server:
if self.__logLevel == value: if self.__logLevel == value:
return return
try: try:
getLogger("fail2ban").setLevel(getattr(logging, value)) ll = getattr(logging, value)
# don't change real log-level if running from the test cases:
getLogger("fail2ban").setLevel(
ll if DEF_LOGTARGET != "INHERITED" or ll < logging.DEBUG else DEF_LOGLEVEL)
self.__logLevel = value self.__logLevel = value
except AttributeError: except AttributeError:
raise ValueError("Invalid log level %r" % value) raise ValueError("Invalid log level %r" % value)

View File

@ -27,7 +27,7 @@ __license__ = "GPL"
import time import time
import json import json
from ..helpers import getLogger from ..helpers import getLogger, logging
from .. import version from .. import version
# Gets the instance of the logger. # Gets the instance of the logger.
@ -52,13 +52,14 @@ class Transmitter:
def proceed(self, command): def proceed(self, command):
# Deserialize object # Deserialize object
logSys.debug("Command: %r", command) logSys.log(5, "Command: %r", command)
try: try:
ret = self.__commandHandler(command) ret = self.__commandHandler(command)
ack = 0, ret ack = 0, ret
except Exception as e: except Exception as e:
logSys.warning("Command %r has failed. Received %r" logSys.warning("Command %r has failed. Received %r",
% (command, e)) command, e,
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
ack = 1, e ack = 1, e
return ack return ack
@ -72,8 +73,8 @@ class Transmitter:
return "pong" return "pong"
elif command[0] == "add": elif command[0] == "add":
name = command[1] name = command[1]
if name == "all": if name == "--all":
raise Exception("Reserved name") raise Exception("Reserved name %r" % (name,))
try: try:
backend = command[2] backend = command[2]
except IndexError: except IndexError:
@ -87,12 +88,31 @@ class Transmitter:
elif command[0] == "stop": elif command[0] == "stop":
if len(command) == 1: if len(command) == 1:
self.__server.quit() self.__server.quit()
elif command[1] == "all": elif command[1] == "--all":
self.__server.stopAllJail() self.__server.stopAllJail()
else: else:
name = command[1] name = command[1]
self.__server.stopJail(name) self.__server.stopJail(name)
return None return None
elif command[0] == "reload":
opts = command[1:3]
try:
self.__server.reloadJails(*opts, begin=True)
for cmd in command[3]:
self.__commandHandler(cmd)
finally:
self.__server.reloadJails(*opts, begin=False)
return None
elif len(command) >= 2 and command[0] == "unban":
# unban in all jails:
value = command[1:]
# if all ips:
if len(value) == 1 and value[0] == "--all":
self.__server.setUnbanIP()
return
for value in value:
self.__server.setUnbanIP(None, value)
return None
elif command[0] == "echo": elif command[0] == "echo":
return command[1:] return command[1:]
elif command[0] == "sleep": elif command[0] == "sleep":
@ -265,7 +285,7 @@ class Transmitter:
action = self.__server.getAction(name, actionname) action = self.__server.getAction(name, actionname)
if multiple: if multiple:
for cmd in command[3]: for cmd in command[3]:
logSys.debug(" %r", cmd) logSys.log(5, " %r", cmd)
actionkey = cmd[0] actionkey = cmd[0]
if callable(getattr(action, actionkey, None)): if callable(getattr(action, actionkey, None)):
actionvalue = json.loads(cmd[1]) if len(cmd)>1 else {} actionvalue = json.loads(cmd[1]) if len(cmd)>1 else {}

View File

@ -43,7 +43,7 @@ from .. import protocol
from ..server import server from ..server import server
from ..server.mytime import MyTime from ..server.mytime import MyTime
from ..server.utils import Utils from ..server.utils import Utils
from .utils import LogCaptureTestCase, with_tmpdir, shutil, logging from .utils import LogCaptureTestCase, logSys as DefLogSys, with_tmpdir, shutil, logging
from ..helpers import getLogger from ..helpers import getLogger
@ -69,7 +69,8 @@ fail2bancmdline.logSys = \
fail2banclient.logSys = \ fail2banclient.logSys = \
fail2banserver.logSys = logSys fail2banserver.logSys = logSys
server.DEF_LOGTARGET = "/dev/null" SRV_DEF_LOGTARGET = server.DEF_LOGTARGET
SRV_DEF_LOGLEVEL = server.DEF_LOGLEVEL
def _test_output(*args): def _test_output(*args):
logSys.info(args[0]) logSys.info(args[0])
@ -111,17 +112,25 @@ fail2bancmdline.PRODUCTION = \
fail2banserver.PRODUCTION = False fail2banserver.PRODUCTION = False
def _out_file(fn): def _out_file(fn, handle=logSys.debug):
"""Helper which outputs content of the file at HEAVYDEBUG loglevels""" """Helper which outputs content of the file at HEAVYDEBUG loglevels"""
logSys.debug('---- ' + fn + ' ----') handle('---- ' + fn + ' ----')
for line in fileinput.input(fn): for line in fileinput.input(fn):
line = line.rstrip('\n') line = line.rstrip('\n')
logSys.debug(line) handle(line)
logSys.debug('-'*30) handle('-'*30)
def _start_params(tmp, use_stock=False, logtarget="/dev/null"): def _write_file(fn, mode, *lines):
f = open(fn, mode)
f.write('\n'.join(lines))
f.close()
def _start_params(tmp, use_stock=False, logtarget="/dev/null", db=":memory:"):
cfg = pjoin(tmp, "config") cfg = pjoin(tmp, "config")
if db == 'auto':
db = pjoin(tmp, "f2b-db.sqlite3")
if use_stock and STOCK: if use_stock and STOCK:
# copy config (sub-directories as alias): # copy config (sub-directories as alias):
def ig_dirs(dir, files): def ig_dirs(dir, files):
@ -147,8 +156,7 @@ def _start_params(tmp, use_stock=False, logtarget="/dev/null"):
else: else:
# just empty config directory without anything (only fail2ban.conf/jail.conf): # just empty config directory without anything (only fail2ban.conf/jail.conf):
os.mkdir(cfg) os.mkdir(cfg)
f = open(pjoin(cfg, "fail2ban.conf"), "w") _write_file(pjoin(cfg, "fail2ban.conf"), "w",
f.write('\n'.join((
"[Definition]", "[Definition]",
"loglevel = INFO", "loglevel = INFO",
"logtarget = " + logtarget, "logtarget = " + logtarget,
@ -156,19 +164,16 @@ def _start_params(tmp, use_stock=False, logtarget="/dev/null"):
"socket = " + pjoin(tmp, "f2b.sock"), "socket = " + pjoin(tmp, "f2b.sock"),
"pidfile = " + pjoin(tmp, "f2b.pid"), "pidfile = " + pjoin(tmp, "f2b.pid"),
"backend = polling", "backend = polling",
"dbfile = :memory:", "dbfile = " + db,
"dbpurgeage = 1d", "dbpurgeage = 1d",
"", "",
))) )
f.close() _write_file(pjoin(cfg, "jail.conf"), "w",
f = open(pjoin(cfg, "jail.conf"), "w")
f.write('\n'.join((
"[INCLUDES]", "", "[INCLUDES]", "",
"[DEFAULT]", "", "[DEFAULT]", "",
"", "",
))) )
f.close() if DefLogSys.level < logging.DEBUG: # if HEAVYDEBUG
if logSys.level < logging.DEBUG: # if HEAVYDEBUG
_out_file(pjoin(cfg, "fail2ban.conf")) _out_file(pjoin(cfg, "fail2ban.conf"))
_out_file(pjoin(cfg, "jail.conf")) _out_file(pjoin(cfg, "jail.conf"))
# parameters (sock/pid and config, increase verbosity, set log, etc.): # parameters (sock/pid and config, increase verbosity, set log, etc.):
@ -238,19 +243,78 @@ def with_kill_srv(f):
_kill_srv(pidfile) _kill_srv(pidfile)
return wrapper return wrapper
def with_foreground_server_thread(startextra={}):
"""Helper to decorate tests uses foreground server (as thread), started directly in test-cases
To be used only in subclasses
"""
def _deco_wrapper(f):
@with_tmpdir
@wraps(f)
def wrapper(self, tmp, *args, **kwargs):
th = None
phase = dict()
try:
# started directly here, so prevent overwrite test cases logger with "INHERITED"
startparams = _start_params(tmp, logtarget="INHERITED", **startextra)
# because foreground block execution - start it in thread:
th = Thread(
name="_TestCaseWorker",
target=self._testStartForeground,
args=(tmp, startparams, phase)
)
th.daemon = True
th.start()
try:
# wait for start thread:
Utils.wait_for(lambda: phase.get('start', None) is not None, MAX_WAITTIME)
self.assertTrue(phase.get('start', None))
# wait for server (socket and ready):
self._wait_for_srv(tmp, True, startparams=startparams)
DefLogSys.info('=== within server: begin ===')
self.pruneLog()
# several commands to server in body of decorated function:
return f(self, tmp, startparams, *args, **kwargs)
finally:
DefLogSys.info('=== within server: end. ===')
self.pruneLog()
# stop:
self.execSuccess(startparams, "stop")
# wait for end:
Utils.wait_for(lambda: phase.get('end', None) is not None, MAX_WAITTIME)
self.assertTrue(phase.get('end', None))
self.assertLogged("Shutdown successful", "Exiting Fail2ban")
finally:
if th:
# we start client/server directly in current process (new thread),
# so don't kill (same process) - if success, just wait for end of worker:
if phase.get('end', None):
th.join()
return wrapper
return _deco_wrapper
class Fail2banClientServerBase(LogCaptureTestCase): class Fail2banClientServerBase(LogCaptureTestCase):
_orig_exit = Fail2banCmdLine._exit _orig_exit = Fail2banCmdLine._exit
def _setLogLevel(self, *args, **kwargs):
pass
def setUp(self): def setUp(self):
"""Call before every test case.""" """Call before every test case."""
LogCaptureTestCase.setUp(self) LogCaptureTestCase.setUp(self)
# prevent to switch the logging in the test cases (use inherited one):
server.DEF_LOGTARGET = "INHERITED"
server.DEF_LOGLEVEL = DefLogSys.level
Fail2banCmdLine._exit = staticmethod(self._test_exit) Fail2banCmdLine._exit = staticmethod(self._test_exit)
def tearDown(self): def tearDown(self):
"""Call after every test case.""" """Call after every test case."""
Fail2banCmdLine._exit = self._orig_exit Fail2banCmdLine._exit = self._orig_exit
# restore server log target:
server.DEF_LOGTARGET = SRV_DEF_LOGTARGET
server.DEF_LOGLEVEL = SRV_DEF_LOGLEVEL
LogCaptureTestCase.tearDown(self) LogCaptureTestCase.tearDown(self)
@staticmethod @staticmethod
@ -304,54 +368,6 @@ class Fail2banClientServerBase(LogCaptureTestCase):
phase['end'] = True phase['end'] = True
logSys.debug("end of test worker") logSys.debug("end of test worker")
def with_foreground_server_thread(startextra={}):
"""Helper to decorate tests uses foreground server (as thread), started directly in test-cases
To be used only in subclasses
"""
def _deco_wrapper(f):
@with_tmpdir
@wraps(f)
def wrapper(self, tmp, *args, **kwargs):
th = None
phase = dict()
try:
# started directly here, so prevent overwrite test cases logger with "INHERITED"
startparams = _start_params(tmp, logtarget="INHERITED", **startextra)
# because foreground block execution - start it in thread:
th = Thread(
name="_TestCaseWorker",
target=self._testStartForeground,
args=(tmp, startparams, phase)
)
th.daemon = True
th.start()
try:
# wait for start thread:
Utils.wait_for(lambda: phase.get('start', None) is not None, MAX_WAITTIME)
self.assertTrue(phase.get('start', None))
# wait for server (socket and ready):
self._wait_for_srv(tmp, True, startparams=startparams)
self.pruneLog()
# several commands to server in body of decorated function:
return f(self, tmp, startparams, *args, **kwargs)
finally:
self.pruneLog()
# stop:
self.execSuccess(startparams, "stop")
# wait for end:
Utils.wait_for(lambda: phase.get('end', None) is not None, MAX_WAITTIME)
self.assertTrue(phase.get('end', None))
self.assertLogged("Shutdown successful", "Exiting Fail2ban")
finally:
if th:
# we start client/server directly in current process (new thread),
# so don't kill (same process) - if success, just wait for end of worker:
if phase.get('end', None):
th.join()
return wrapper
return _deco_wrapper
@with_foreground_server_thread() @with_foreground_server_thread()
def testStartForeground(self, tmp, startparams): def testStartForeground(self, tmp, startparams):
# several commands to server: # several commands to server:
@ -522,6 +538,24 @@ class Fail2banClientTest(Fail2banClientServerBase):
self.assertLogged("Usage: ") self.assertLogged("Usage: ")
self.pruneLog() self.pruneLog()
@with_tmpdir
def testClientFailCommands(self, tmp):
# started directly here, so prevent overwrite test cases logger with "INHERITED"
startparams = _start_params(tmp, logtarget="INHERITED")
# not started:
self.execFailed(startparams,
"reload", "jail")
self.assertLogged("Could not find server")
self.pruneLog()
# unexpected arg:
self.execFailed(startparams,
"--async", "reload", "--xxx", "jail")
self.assertLogged("Unexpected argument(s) for reload:")
self.pruneLog()
def testVisualWait(self): def testVisualWait(self):
sleeptime = 0.035 sleeptime = 0.035
for verbose in (2, 0): for verbose in (2, 0):
@ -626,3 +660,242 @@ class Fail2banServerTest(Fail2banClientServerBase):
# again: # again:
self.assertTrue(_kill_srv(tmp)) self.assertTrue(_kill_srv(tmp))
self.assertLogged("cleanup: no pidfile for") self.assertLogged("cleanup: no pidfile for")
@with_foreground_server_thread(startextra={'db': 'auto'})
def testServerReloadTest(self, tmp, startparams):
"""Very complicated test-case, that expected running server (foreground in thread).
In this test-case, each phase is related from previous one,
so it cannot be splitted in multiple test cases.
It uses file database (instead of :memory:), to restore bans and log-file positions,
after restart/reload between phases.
"""
cfg = pjoin(tmp, "config")
test1log = pjoin(tmp, "test1.log")
test2log = pjoin(tmp, "test2.log")
test3log = pjoin(tmp, "test3.log")
def _write_jail_cfg(enabled=[1, 2]):
_write_file(pjoin(cfg, "jail.conf"), "w",
"[INCLUDES]", "",
"[DEFAULT]", "",
"maxretry = 3",
"findtime = 10m",
"failregex = ^\s*failure (401|403) from <HOST>",
"",
"[test-jail1]", "backend = polling", "filter =", "action =",
"logpath = " + test1log,
" " + test2log if 2 in enabled else "",
" " + test3log if 2 in enabled else "",
"failregex = ^\s*failure (401|403) from <HOST>",
" ^\s*error (401|403) from <HOST>" if 2 in enabled else "",
"enabled = true" if 1 in enabled else "",
"",
"[test-jail2]", "backend = polling", "filter =", "action =",
"logpath = " + test2log,
"enabled = true" if 2 in enabled else "",
)
if DefLogSys.level < logging.DEBUG: # if HEAVYDEBUG
_out_file(pjoin(cfg, "jail.conf"))
_write_jail_cfg(enabled=[1])
_write_file(test1log, "w", *((str(int(MyTime.time())) + " failure 401 from 192.0.2.1: test 1",) * 3))
_write_file(test2log, "w")
_write_file(test3log, "w")
# reload and wait for ban:
self.pruneLog("[test-phase 1]")
if DefLogSys.level < logging.DEBUG: # if HEAVYDEBUG
_out_file(test1log)
self.execSuccess(startparams, "reload")
self.assertTrue(
Utils.wait_for(lambda: self._is_logged("[test-jail1] Ban 192.0.2.1"), MAX_WAITTIME / 5.0))
self.assertLogged("Added logfile: %r" % test1log)
# enable both jails, 3 logs for jail1, etc...
# truncate test-log - we should not find unban/ban again by reload:
self.pruneLog("[test-phase 2a]")
_write_jail_cfg()
_write_file(test1log, "w+")
if DefLogSys.level < logging.DEBUG: # if HEAVYDEBUG
_out_file(test1log)
self.execSuccess(startparams, "reload")
self.assertTrue(
Utils.wait_for(lambda: self._is_logged("Reload finished."), MAX_WAITTIME / 5.0))
# test not unbanned / banned again:
self.assertNotLogged(
"[test-jail1] Unban 192.0.2.1",
"[test-jail1] Ban 192.0.2.1", all=True)
# test 2 new log files:
self.assertLogged(
"Added logfile: %r" % test2log,
"Added logfile: %r" % test3log, all=True)
# test 1 new jail:
self.assertLogged(
"Creating new jail 'test-jail2'",
"Jail 'test-jail2' started", all=True)
# write new failures:
self.pruneLog("[test-phase 2b]")
_write_file(test2log, "w+", *(
(str(int(MyTime.time())) + " error 403 from 192.0.2.2: test 2",) * 3 +
(str(int(MyTime.time())) + " error 403 from 192.0.2.3: test 2",) * 3 +
(str(int(MyTime.time())) + " failure 401 from 192.0.2.4: test 2",) * 3
))
if DefLogSys.level < logging.DEBUG: # if HEAVYDEBUG
_out_file(test2log)
# test all will be found in jail1 and one in jail2:
self.assertTrue(
Utils.wait_for(lambda: \
self._is_logged("[test-jail1] Ban 192.0.2.2") and
self._is_logged("[test-jail1] Ban 192.0.2.3") and
self._is_logged("[test-jail1] Ban 192.0.2.4") and
self._is_logged("[test-jail2] Ban 192.0.2.4")
, MAX_WAITTIME / 5.0))
# test ips at all not visible for jail2:
self.assertNotLogged(
"[test-jail2] Found 192.0.2.2",
"[test-jail2] Ban 192.0.2.2",
"[test-jail2] Found 192.0.2.3",
"[test-jail2] Ban 192.0.2.3", all=True)
# rotate logs:
_write_file(test1log, "w+")
_write_file(test2log, "w+")
# restart jail without unban all:
self.pruneLog("[test-phase 2c]")
self.execSuccess(startparams,
"restart", "test-jail2")
self.assertTrue(
Utils.wait_for(lambda: \
self._is_logged("Jail 'test-jail2' started") and
self._is_logged("[test-jail2] Ban 192.0.2.4")
, MAX_WAITTIME / 5.0))
# stop/start and ban/unban:
self.assertLogged(
"Jail 'test-jail2' stopped",
"Jail 'test-jail2' started",
"[test-jail2] Unban 192.0.2.4",
"[test-jail2] Ban 192.0.2.4", all=True
)
# restart jail with unban all:
self.pruneLog("[test-phase 2d]")
self.execSuccess(startparams,
"restart", "--unban", "test-jail2")
self.assertTrue(
Utils.wait_for(lambda: self._is_logged("Jail 'test-jail2' started"),
MAX_WAITTIME / 5.0))
self.assertLogged(
"Jail 'test-jail2' stopped",
"Jail 'test-jail2' started",
"[test-jail2] Unban 192.0.2.4", all=True
)
# no more ban (unbanned all):
self.assertNotLogged(
"[test-jail2] Ban 192.0.2.4", all=True
)
# reload jail1 without restart (without ban/unban):
self.pruneLog("[test-phase 3]")
self.execSuccess(startparams, "reload", "test-jail1")
self.assertTrue(
Utils.wait_for(lambda: self._is_logged("Reload finished."), MAX_WAITTIME / 5.0))
self.assertLogged(
"Reload jail 'test-jail1'",
"Jail 'test-jail1' reloaded", all=True)
self.assertNotLogged(
"Reload jail 'test-jail2'",
"Jail 'test-jail2' reloaded",
"Jail 'test-jail1' started", all=True
)
# whole reload, but this time with jail1 only (jail2 should be stopped via configuration):
self.pruneLog("[test-phase 4]")
_write_jail_cfg(enabled=[1])
self.execSuccess(startparams, "reload")
self.assertTrue(
Utils.wait_for(lambda: self._is_logged("Reload finished."), MAX_WAITTIME / 5.0))
# test both jails should be reloaded:
self.assertLogged(
"Reload jail 'test-jail1'")
# test jail2 goes down:
self.assertLogged(
"Stopping jail 'test-jail2'",
"Jail 'test-jail2' stopped", all=True)
# test 2 log files removed:
self.assertLogged(
"Removed logfile: %r" % test2log,
"Removed logfile: %r" % test3log, all=True)
# now write failures again and check already banned (jail1 was alive the whole time) and new bans occurred (jail1 was alive the whole time):
self.pruneLog("[test-phase 5]")
_write_file(test1log, "w+", *(
(str(int(MyTime.time())) + " failure 401 from 192.0.2.1: test 5",) * 3 +
(str(int(MyTime.time())) + " error 403 from 192.0.2.5: test 5",) * 3 +
(str(int(MyTime.time())) + " failure 401 from 192.0.2.6: test 5",) * 3
))
if DefLogSys.level < logging.DEBUG: # if HEAVYDEBUG
_out_file(test1log)
# test "failure" regexp still available:
self.assertTrue(
Utils.wait_for(lambda: \
self._is_logged("[test-jail1] 192.0.2.1 already banned") and
self._is_logged("[test-jail1] Ban 192.0.2.6")
, MAX_WAITTIME / 5.0))
self.assertLogged(
"[test-jail1] Found 192.0.2.1",
"[test-jail1] Found 192.0.2.6", all=True
)
# test "error" regexp no more available:
self.assertNotLogged("[test-jail1] Found 192.0.2.5")
# unban single ips:
self.pruneLog("[test-phase 6]")
self.execSuccess(startparams,
"--async", "unban", "192.0.2.5", "192.0.2.6")
self.assertLogged(
"192.0.2.5 is not banned",
"[test-jail1] Unban 192.0.2.6", all=True
)
# reload all (one jail) with unban all:
self.pruneLog("[test-phase 7]")
self.execSuccess(startparams,
"reload", "--unban")
self.assertTrue(
Utils.wait_for(lambda: self._is_logged("Reload finished."), MAX_WAITTIME / 5.0))
# reloads unbanned all:
self.assertLogged(
"Jail 'test-jail1' reloaded",
"[test-jail1] Unban 192.0.2.1",
"[test-jail1] Unban 192.0.2.2",
"[test-jail1] Unban 192.0.2.3",
"[test-jail1] Unban 192.0.2.4", all=True
)
# no restart occurred, no more ban (unbanned all using option "--unban"):
self.assertNotLogged(
"Jail 'test-jail1' stopped",
"Jail 'test-jail1' started",
"[test-jail1] Ban 192.0.2.1",
"[test-jail1] Ban 192.0.2.2",
"[test-jail1] Ban 192.0.2.3",
"[test-jail1] Ban 192.0.2.4", all=True
)
# several small cases:
self.pruneLog("[test-phase end-1]")
# wrong jail (not-started):
self.execFailed(startparams,
"--async", "reload", "test-jail2")
self.assertLogged("the jail 'test-jail2' does not exist")
self.pruneLog()
# unavailable jail (but exit 0), using --if-exists option:
self.execSuccess(startparams,
"--async", "reload", "--if-exists", "test-jail2")
self.assertNotLogged(
"Creating new jail 'test-jail2'",
"Jail 'test-jail2' started", all=True)
self.pruneLog()

View File

@ -240,7 +240,7 @@ class Transmitter(TransmitterBase):
self.transm.proceed(["add", self.jailName, "polling"])[0], 1) self.transm.proceed(["add", self.jailName, "polling"])[0], 1)
# All name is reserved # All name is reserved
self.assertEqual( self.assertEqual(
self.transm.proceed(["add", "all", "polling"])[0], 1) self.transm.proceed(["add", "--all", "polling"])[0], 1)
def testStartStopJail(self): def testStartStopJail(self):
self.assertEqual( self.assertEqual(
@ -267,7 +267,7 @@ class Transmitter(TransmitterBase):
self.assertTrue( Utils.wait_for( self.assertTrue( Utils.wait_for(
lambda: self.server.isAlive(2) and not isinstance(self.transm.proceed(["status", self.jailName]), RuntimeError), lambda: self.server.isAlive(2) and not isinstance(self.transm.proceed(["status", self.jailName]), RuntimeError),
3) ) 3) )
self.assertEqual(self.transm.proceed(["stop", "all"]), (0, None)) self.assertEqual(self.transm.proceed(["stop", "--all"]), (0, None))
self.assertTrue( Utils.wait_for( lambda: not len(self.server._Server__jails), 3) ) self.assertTrue( Utils.wait_for( lambda: not len(self.server._Server__jails), 3) )
self.assertNotIn(self.jailName, self.server._Server__jails) self.assertNotIn(self.jailName, self.server._Server__jails)
self.assertNotIn("TestJail2", self.server._Server__jails) self.assertNotIn("TestJail2", self.server._Server__jails)

View File

@ -119,6 +119,7 @@ def getOptParser(doc=""):
def initProcess(opts): def initProcess(opts):
# Logger: # Logger:
global logSys
logSys = getLogger("fail2ban") logSys = getLogger("fail2ban")
# Numerical level of verbosity corresponding to a log "level" # Numerical level of verbosity corresponding to a log "level"
@ -577,7 +578,8 @@ class LogCaptureTestCase(unittest.TestCase):
print("") print("")
logSys.handlers += self._old_handlers logSys.handlers += self._old_handlers
logSys.debug('='*10 + ' %s ' + '='*20, self.id()) logSys.debug('='*10 + ' %s ' + '='*20, self.id())
logSys.setLevel(logging.DEBUG) else:
logSys.setLevel(logging.DEBUG)
def tearDown(self): def tearDown(self):
"""Call after every test case.""" """Call after every test case."""
@ -638,8 +640,10 @@ class LogCaptureTestCase(unittest.TestCase):
if s_ in logged: # pragma: no cover if s_ in logged: # pragma: no cover
self.fail("%r was found in the log: ===\n%s===" % (s_, logged)) self.fail("%r was found in the log: ===\n%s===" % (s_, logged))
def pruneLog(self): def pruneLog(self, logphase=None):
self._log.truncate(0) self._log.truncate(0)
if logphase:
logSys.debug('='*5 + ' %s ' + '='*5, logphase)
def getLog(self): def getLog(self):
return self._log.getvalue() return self._log.getvalue()