mirror of https://github.com/fail2ban/fail2ban
temp commit: reload not ready...
parent
f512628af2
commit
b12a3acb06
|
@ -46,7 +46,7 @@ class Beautifier:
|
|||
return self.__inputCmd
|
||||
|
||||
def beautify(self, response):
|
||||
logSys.debug(
|
||||
logSys.log(5,
|
||||
"Beautify " + repr(response) + " with " + repr(self.__inputCmd))
|
||||
inC = self.__inputCmd
|
||||
msg = response
|
||||
|
|
|
@ -91,7 +91,7 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
|||
client = CSocket(self._conf["socket"])
|
||||
ret = client.send(c)
|
||||
if ret[0] == 0:
|
||||
logSys.debug("OK : %r", ret[1])
|
||||
logSys.log(5, "OK : %r", ret[1])
|
||||
if showRet or c[0] == 'echo':
|
||||
output(beautifier.beautify(ret[1]))
|
||||
else:
|
||||
|
@ -104,7 +104,7 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
|||
if showRet or c != ["ping"]:
|
||||
self.__logSocketError()
|
||||
else:
|
||||
logSys.debug(" -- ping failed -- %r", e)
|
||||
logSys.log(5, " -- ping failed -- %r", e)
|
||||
return False
|
||||
except Exception as e: # pragma: no cover
|
||||
if showRet or self._conf["verbose"] > 1:
|
||||
|
@ -226,11 +226,11 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
|||
# prepare: read config, check configuration is valid, etc.:
|
||||
if phase is not None:
|
||||
phase['start'] = True
|
||||
logSys.debug(' client phase %s', phase)
|
||||
logSys.log(5, ' client phase %s', phase)
|
||||
stream = self.__prepareStartServer()
|
||||
if phase is not None:
|
||||
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:
|
||||
return False
|
||||
# configure server with config stream:
|
||||
|
@ -246,6 +246,10 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
|||
# @param cmd the command line
|
||||
|
||||
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":
|
||||
|
||||
ret = self.__startServer(self._conf["background"])
|
||||
|
@ -253,8 +257,12 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
|||
return False
|
||||
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):
|
||||
output(' ## stop ... ')
|
||||
self.__processCommand(['stop'])
|
||||
|
@ -273,9 +281,21 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
|||
return self.__processCommand(['start'])
|
||||
|
||||
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 len(cmd) == 1:
|
||||
jail = 'all'
|
||||
jail = '--all'
|
||||
ret, stream = self.readConfig()
|
||||
else:
|
||||
jail = cmd[1]
|
||||
|
@ -283,9 +303,10 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
|||
# Do not continue if configuration is not 100% valid
|
||||
if not ret:
|
||||
return False
|
||||
self.__processCmd([['stop', jail]], False)
|
||||
# Configure the server
|
||||
return self.__processCmd(stream, True)
|
||||
if self._conf.get("interactive", False):
|
||||
output(' ## reload ... ')
|
||||
# Reconfigure the server
|
||||
return self.__processCmd([['reload', jail, opts, stream]], True)
|
||||
else:
|
||||
logSys.error("Could not find server")
|
||||
return False
|
||||
|
@ -320,7 +341,7 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
|||
maxtime = self._conf["timeout"]
|
||||
# Wait for the server to start (the server has 30 seconds to answer ping)
|
||||
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()
|
||||
with VisualWait(self._conf["verbose"]) as vis:
|
||||
sltime = 0.0125 / 2
|
||||
|
|
|
@ -49,8 +49,11 @@ protocol = [
|
|||
['', "BASIC", ""],
|
||||
["start", "starts the server and the jails"],
|
||||
["restart", "restarts the server"],
|
||||
["reload", "reloads the configuration without restart"],
|
||||
["reload <JAIL>", "reloads the jail <JAIL>"],
|
||||
["restart [--unban] [--if-exists] <JAIL>", "restarts the jail <JAIL> (alias for 'reload --restart ... <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"],
|
||||
["status", "gets the current status of the server"],
|
||||
["ping", "tests if the server is alive"],
|
||||
|
|
|
@ -180,7 +180,7 @@ class Actions(JailThread, Mapping):
|
|||
def getBanTime(self):
|
||||
return self.__banManager.getBanTime()
|
||||
|
||||
def removeBannedIP(self, ip):
|
||||
def removeBannedIP(self, ip=None, db=True, ifexists=False):
|
||||
"""Removes banned IP calling actions' unban method
|
||||
|
||||
Remove a banned IP now, rather than waiting for it to expire,
|
||||
|
@ -188,16 +188,20 @@ class Actions(JailThread, Mapping):
|
|||
|
||||
Parameters
|
||||
----------
|
||||
ip : str or IPAddr
|
||||
The IP address to unban
|
||||
ip : str or IPAddr or None
|
||||
The IP address to unban or all IPs if None
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
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)
|
||||
if self._jail.database is not None:
|
||||
if db and self._jail.database is not None:
|
||||
self._jail.database.delBan(self._jail, ip)
|
||||
# Find the ticket with the IP.
|
||||
ticket = self.__banManager.getTicketByIP(ip)
|
||||
|
@ -205,7 +209,11 @@ class Actions(JailThread, Mapping):
|
|||
# Unban the IP.
|
||||
self.__unBan(ticket)
|
||||
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):
|
||||
"""Main loop for Threading.
|
||||
|
@ -336,14 +344,21 @@ class Actions(JailThread, Mapping):
|
|||
for ticket in self.__banManager.unBanList(MyTime.time()):
|
||||
self.__unBan(ticket)
|
||||
|
||||
def __flushBan(self):
|
||||
def __flushBan(self, db=False):
|
||||
"""Flush the ban list.
|
||||
|
||||
Unban all IP address which are still in the banning 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)
|
||||
return len(lst)
|
||||
|
||||
def __unBan(self, ticket):
|
||||
"""Unbans host corresponding to the ticket.
|
||||
|
|
|
@ -88,8 +88,9 @@ class Filter(JailThread):
|
|||
self.__ignoreCommand = False
|
||||
## Default or preferred encoding (to decode bytes from file or journal):
|
||||
self.__encoding = locale.getpreferredencoding()
|
||||
## Error counter
|
||||
self.__errors = 0
|
||||
## Error counter (protected, so can be used in filter implementations)
|
||||
## if it reached 100 (at once), run-cycle will go idle
|
||||
self._errors = 0
|
||||
## Ticks counter
|
||||
self.ticks = 0
|
||||
|
||||
|
@ -100,6 +101,30 @@ class Filter(JailThread):
|
|||
def __repr__(self):
|
||||
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.
|
||||
#
|
||||
|
@ -119,8 +144,13 @@ class Filter(JailThread):
|
|||
logSys.error(e)
|
||||
raise e
|
||||
|
||||
def delFailRegex(self, index):
|
||||
def delFailRegex(self, index=None):
|
||||
try:
|
||||
# clear all:
|
||||
if index is None:
|
||||
del self.__failRegex[:]
|
||||
return
|
||||
# delete by index:
|
||||
del self.__failRegex[index]
|
||||
except IndexError:
|
||||
logSys.error("Cannot remove regular expression. Index %d is not "
|
||||
|
@ -152,8 +182,13 @@ class Filter(JailThread):
|
|||
logSys.error(e)
|
||||
raise e
|
||||
|
||||
def delIgnoreRegex(self, index):
|
||||
def delIgnoreRegex(self, index=None):
|
||||
try:
|
||||
# clear all:
|
||||
if index is None:
|
||||
del self.__ignoreRegex[:]
|
||||
return
|
||||
# delete by index:
|
||||
del self.__ignoreRegex[index]
|
||||
except IndexError:
|
||||
logSys.error("Cannot remove regular expression. Index %d is not "
|
||||
|
@ -203,7 +238,7 @@ class Filter(JailThread):
|
|||
value = MyTime.str2seconds(value)
|
||||
self.__findTime = value
|
||||
self.failManager.setMaxTime(value)
|
||||
logSys.info("Set findtime = %s" % value)
|
||||
logSys.info(" findtime: %s", value)
|
||||
|
||||
##
|
||||
# Get the time needed to find a failure.
|
||||
|
@ -232,10 +267,10 @@ class Filter(JailThread):
|
|||
template = DatePatternRegex(pattern)
|
||||
self.dateDetector = DateDetector()
|
||||
self.dateDetector.appendTemplate(template)
|
||||
logSys.info("Date pattern set to `%r`: `%s`" %
|
||||
(pattern, template.name))
|
||||
logSys.debug("Date pattern regex for %r: %s" %
|
||||
(pattern, template.regex))
|
||||
logSys.info(" date pattern `%r`: `%s`",
|
||||
pattern, template.name)
|
||||
logSys.debug(" date pattern regex for %r: %s",
|
||||
pattern, template.regex)
|
||||
|
||||
##
|
||||
# Get the date detector pattern, or Default Detectors if not changed
|
||||
|
@ -261,7 +296,7 @@ class Filter(JailThread):
|
|||
|
||||
def setMaxRetry(self, value):
|
||||
self.failManager.setMaxRetry(value)
|
||||
logSys.info("Set maxRetry = %s" % value)
|
||||
logSys.info(" maxRetry: %s", value)
|
||||
|
||||
##
|
||||
# Get the maximum retry value.
|
||||
|
@ -280,7 +315,7 @@ class Filter(JailThread):
|
|||
if int(value) <= 0:
|
||||
raise ValueError("maxlines must be integer greater than zero")
|
||||
self.__lineBufferSize = int(value)
|
||||
logSys.info("Set maxlines = %i" % self.__lineBufferSize)
|
||||
logSys.info(" maxLines: %i", self.__lineBufferSize)
|
||||
|
||||
##
|
||||
# Get the maximum line buffer size.
|
||||
|
@ -300,7 +335,7 @@ class Filter(JailThread):
|
|||
encoding = locale.getpreferredencoding()
|
||||
codecs.lookup(encoding) # Raise LookupError if invalid codec
|
||||
self.__encoding = encoding
|
||||
logSys.info("Set jail log file encoding to %s" % encoding)
|
||||
logSys.info(" encoding: %s" % encoding)
|
||||
return encoding
|
||||
|
||||
##
|
||||
|
@ -375,11 +410,16 @@ class Filter(JailThread):
|
|||
ip = IPAddr(ipstr)
|
||||
|
||||
# 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)
|
||||
|
||||
def delIgnoreIP(self, ip):
|
||||
logSys.debug("Remove %r from ignore list", ip)
|
||||
def delIgnoreIP(self, ip=None):
|
||||
# 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)
|
||||
|
||||
def logIgnoreIp(self, ip, log_ignore, ignore_source="unknown source"):
|
||||
|
@ -483,18 +523,18 @@ class Filter(JailThread):
|
|||
tick = FailTicket(ip, unixTime, lines, data=fail)
|
||||
self.failManager.addFailure(tick)
|
||||
# reset (halve) error counter (successfully processed line):
|
||||
if self.__errors:
|
||||
self.__errors //= 2
|
||||
if self._errors:
|
||||
self._errors //= 2
|
||||
except Exception as e:
|
||||
logSys.error("Failed to process line: %r, caught exception: %r", line, e,
|
||||
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||
# 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):
|
||||
time.sleep(self.sleeptime)
|
||||
if self.__errors >= 100:
|
||||
logSys.error("Too many errors at once (%s), going idle", self.__errors)
|
||||
self.__errors //= 2
|
||||
if self._errors >= 100:
|
||||
logSys.error("Too many errors at once (%s), going idle", self._errors)
|
||||
self._errors //= 2
|
||||
self.idle = True
|
||||
|
||||
##
|
||||
|
@ -657,7 +697,10 @@ class FileFilter(Filter):
|
|||
|
||||
def addLogPath(self, path, tail=False, autoSeek=True):
|
||||
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:
|
||||
log = FileContainer(path, self.getLogEncoding(), tail)
|
||||
db = self.jail.database
|
||||
|
@ -666,7 +709,7 @@ class FileFilter(Filter):
|
|||
if lastpos and not tail:
|
||||
log.setPos(lastpos)
|
||||
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 default, seek to "current time" - "find time":
|
||||
if isinstance(autoSeek, bool):
|
||||
|
@ -692,7 +735,7 @@ class FileFilter(Filter):
|
|||
db = self.jail.database
|
||||
if db is not None:
|
||||
db.updateLog(self.jail, log)
|
||||
logSys.info("Removed logfile = %s" % path)
|
||||
logSys.info("Removed logfile: %r" % path)
|
||||
self._delLogPath(path)
|
||||
return
|
||||
|
||||
|
@ -1055,10 +1098,14 @@ _decode_line_warn = {}
|
|||
|
||||
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
|
||||
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
|
||||
|
||||
def getJournalMatch(self, match): # pragma: no cover - Base class, not used
|
||||
|
|
|
@ -31,7 +31,7 @@ from .failmanager import FailManagerEmpty
|
|||
from .filter import FileFilter
|
||||
from .mytime import MyTime
|
||||
from .utils import Utils
|
||||
from ..helpers import getLogger
|
||||
from ..helpers import getLogger, logging
|
||||
|
||||
|
||||
# Gets the instance of the logger.
|
||||
|
@ -137,28 +137,34 @@ class FilterPoll(FileFilter):
|
|||
try:
|
||||
logStats = os.stat(filename)
|
||||
stats = logStats.st_mtime, logStats.st_ino, logStats.st_size
|
||||
pstats = self.__prevStats.get(filename, ())
|
||||
self.__file404Cnt[filename] = 0
|
||||
pstats = self.__prevStats.get(filename, (0))
|
||||
if logSys.getEffectiveLevel() <= 7:
|
||||
# we do not want to waste time on strftime etc if not necessary
|
||||
dt = logStats.st_mtime - pstats[0]
|
||||
logSys.log(7, "Checking %s for being modified. Previous/current stats: %s / %s. dt: %s",
|
||||
filename, pstats, stats, dt)
|
||||
# os.system("stat %s | grep Modify" % filename)
|
||||
self.__file404Cnt[filename] = 0
|
||||
if pstats == stats:
|
||||
return False
|
||||
logSys.debug("%s has been modified", filename)
|
||||
self.__prevStats[filename] = stats
|
||||
return True
|
||||
except OSError as e:
|
||||
logSys.error("Unable to get stat on %s because of: %s"
|
||||
% (filename, e))
|
||||
except Exception as e:
|
||||
# stil alive (may be deleted because multi-threaded):
|
||||
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
|
||||
if self.__file404Cnt[filename] > 2:
|
||||
logSys.warning("Too many errors. Setting the jail idle")
|
||||
if self.jail is not None:
|
||||
self.jail.idle = True
|
||||
else:
|
||||
logSys.warning("No jail is assigned to %s" % self)
|
||||
self._errors += 1
|
||||
if self.__file404Cnt[filename] > 50:
|
||||
logSys.warning("Too many errors. Remove file %r from monitoring process", filename)
|
||||
self.__file404Cnt[filename] = 0
|
||||
self.delLogPath(filename)
|
||||
return False
|
||||
|
|
|
@ -154,12 +154,16 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
|||
#
|
||||
# @param match journalctl syntax matches
|
||||
|
||||
def delJournalMatch(self, match):
|
||||
if match in self.__matches:
|
||||
def delJournalMatch(self, match=None):
|
||||
# clear all:
|
||||
if match is None:
|
||||
del self.__matches[:]
|
||||
# delete by index:
|
||||
elif match in self.__matches:
|
||||
del self.__matches[self.__matches.index(match)]
|
||||
self.resetJournalMatches()
|
||||
else:
|
||||
raise ValueError("Match not found")
|
||||
self.resetJournalMatches()
|
||||
logSys.info("Removed journal match for: %r" % " ".join(match))
|
||||
|
||||
##
|
||||
|
|
|
@ -79,6 +79,7 @@ class Jail(object):
|
|||
logSys.info("Creating new jail '%s'" % self.name)
|
||||
if backend is not None:
|
||||
self._setBackend(backend)
|
||||
self.backend = backend
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%r)" % (self.__class__.__name__, self.name)
|
||||
|
@ -220,7 +221,7 @@ class Jail(object):
|
|||
jail=self, bantime=self.actions.getBanTime()):
|
||||
if not self.filter.inIgnoreIPList(ticket.getIP(), log_ignore=True):
|
||||
self.__queue.put(ticket)
|
||||
logSys.info("Jail '%s' started" % self.name)
|
||||
logSys.info("Jail %r started", self.name)
|
||||
|
||||
def stop(self):
|
||||
"""Stop the jail, by stopping filter and actions threads.
|
||||
|
|
|
@ -62,14 +62,15 @@ class Jails(Mapping):
|
|||
DuplicateJailException
|
||||
If jail name is already present.
|
||||
"""
|
||||
try:
|
||||
self.__lock.acquire()
|
||||
with self.__lock:
|
||||
if name in self._jails:
|
||||
raise DuplicateJailException(name)
|
||||
if noduplicates:
|
||||
raise DuplicateJailException(name)
|
||||
else:
|
||||
self._jails[name] = Jail(name, backend, db)
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
||||
def exists(self, name):
|
||||
return name in self._jails
|
||||
|
||||
def __getitem__(self, name):
|
||||
try:
|
||||
|
|
|
@ -67,6 +67,7 @@ class Server:
|
|||
self.__db = None
|
||||
self.__daemon = daemon
|
||||
self.__transm = Transmitter(self)
|
||||
self.__reload_state = {}
|
||||
#self.__asyncServer = AsyncServer(self.__transm)
|
||||
self.__asyncServer = None
|
||||
self.__logLevel = None
|
||||
|
@ -184,41 +185,105 @@ class Server:
|
|||
self.quit = lambda: False
|
||||
|
||||
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:
|
||||
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:
|
||||
self.__db.delJail(self.__jails[name])
|
||||
self.__db.delJail(jail)
|
||||
del self.__jails[name]
|
||||
|
||||
def startJail(self, name):
|
||||
try:
|
||||
self.__lock.acquire()
|
||||
if not self.__jails[name].isAlive():
|
||||
self.__jails[name].start()
|
||||
finally:
|
||||
self.__lock.release()
|
||||
with self.__lock:
|
||||
jail = self.__jails[name]
|
||||
if not jail.isAlive():
|
||||
jail.start()
|
||||
elif name in self.__reload_state:
|
||||
logSys.info("Jail %r reloaded", name)
|
||||
del self.__reload_state[name]
|
||||
if jail.idle:
|
||||
jail.idle = False
|
||||
|
||||
def stopJail(self, name):
|
||||
logSys.debug("Stopping jail %s" % name)
|
||||
try:
|
||||
self.__lock.acquire()
|
||||
if self.__jails[name].isAlive():
|
||||
self.__jails[name].stop()
|
||||
self.delJail(name)
|
||||
finally:
|
||||
self.__lock.release()
|
||||
with self.__lock:
|
||||
self.delJail(name, stop=True)
|
||||
|
||||
def stopAllJail(self):
|
||||
logSys.info("Stopping all jails")
|
||||
try:
|
||||
self.__lock.acquire()
|
||||
for jail in self.__jails.keys():
|
||||
self.stopJail(jail)
|
||||
finally:
|
||||
self.__lock.release()
|
||||
with self.__lock:
|
||||
for name in self.__jails.keys():
|
||||
self.delJail(name, stop=True)
|
||||
|
||||
def reloadJails(self, name, opts, begin):
|
||||
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):
|
||||
self.__jails[name].idle = value
|
||||
|
@ -309,7 +374,7 @@ class Server:
|
|||
logSys.debug(" failregex: %r", value)
|
||||
flt.addFailRegex(value)
|
||||
|
||||
def delFailRegex(self, name, index):
|
||||
def delFailRegex(self, name, index=None):
|
||||
self.__jails[name].filter.delFailRegex(index)
|
||||
|
||||
def getFailRegex(self, name):
|
||||
|
@ -368,8 +433,20 @@ class Server:
|
|||
def setBanIP(self, name, value):
|
||||
return self.__jails[name].filter.addBannedIP(value)
|
||||
|
||||
def setUnbanIP(self, name, value):
|
||||
self.__jails[name].actions.removeBannedIP(value)
|
||||
def setUnbanIP(self, name=None, value=None):
|
||||
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):
|
||||
return self.__jails[name].actions.getBanTime()
|
||||
|
@ -420,7 +497,10 @@ class Server:
|
|||
if self.__logLevel == value:
|
||||
return
|
||||
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
|
||||
except AttributeError:
|
||||
raise ValueError("Invalid log level %r" % value)
|
||||
|
|
|
@ -27,7 +27,7 @@ __license__ = "GPL"
|
|||
import time
|
||||
import json
|
||||
|
||||
from ..helpers import getLogger
|
||||
from ..helpers import getLogger, logging
|
||||
from .. import version
|
||||
|
||||
# Gets the instance of the logger.
|
||||
|
@ -52,13 +52,14 @@ class Transmitter:
|
|||
|
||||
def proceed(self, command):
|
||||
# Deserialize object
|
||||
logSys.debug("Command: %r", command)
|
||||
logSys.log(5, "Command: %r", command)
|
||||
try:
|
||||
ret = self.__commandHandler(command)
|
||||
ack = 0, ret
|
||||
except Exception as e:
|
||||
logSys.warning("Command %r has failed. Received %r"
|
||||
% (command, e))
|
||||
logSys.warning("Command %r has failed. Received %r",
|
||||
command, e,
|
||||
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||
ack = 1, e
|
||||
return ack
|
||||
|
||||
|
@ -72,8 +73,8 @@ class Transmitter:
|
|||
return "pong"
|
||||
elif command[0] == "add":
|
||||
name = command[1]
|
||||
if name == "all":
|
||||
raise Exception("Reserved name")
|
||||
if name == "--all":
|
||||
raise Exception("Reserved name %r" % (name,))
|
||||
try:
|
||||
backend = command[2]
|
||||
except IndexError:
|
||||
|
@ -87,12 +88,31 @@ class Transmitter:
|
|||
elif command[0] == "stop":
|
||||
if len(command) == 1:
|
||||
self.__server.quit()
|
||||
elif command[1] == "all":
|
||||
elif command[1] == "--all":
|
||||
self.__server.stopAllJail()
|
||||
else:
|
||||
name = command[1]
|
||||
self.__server.stopJail(name)
|
||||
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":
|
||||
return command[1:]
|
||||
elif command[0] == "sleep":
|
||||
|
@ -265,7 +285,7 @@ class Transmitter:
|
|||
action = self.__server.getAction(name, actionname)
|
||||
if multiple:
|
||||
for cmd in command[3]:
|
||||
logSys.debug(" %r", cmd)
|
||||
logSys.log(5, " %r", cmd)
|
||||
actionkey = cmd[0]
|
||||
if callable(getattr(action, actionkey, None)):
|
||||
actionvalue = json.loads(cmd[1]) if len(cmd)>1 else {}
|
||||
|
|
|
@ -43,7 +43,7 @@ from .. import protocol
|
|||
from ..server import server
|
||||
from ..server.mytime import MyTime
|
||||
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
|
||||
|
||||
|
@ -69,7 +69,8 @@ fail2bancmdline.logSys = \
|
|||
fail2banclient.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):
|
||||
logSys.info(args[0])
|
||||
|
@ -111,17 +112,25 @@ fail2bancmdline.PRODUCTION = \
|
|||
fail2banserver.PRODUCTION = False
|
||||
|
||||
|
||||
def _out_file(fn):
|
||||
def _out_file(fn, handle=logSys.debug):
|
||||
"""Helper which outputs content of the file at HEAVYDEBUG loglevels"""
|
||||
logSys.debug('---- ' + fn + ' ----')
|
||||
handle('---- ' + fn + ' ----')
|
||||
for line in fileinput.input(fn):
|
||||
line = line.rstrip('\n')
|
||||
logSys.debug(line)
|
||||
logSys.debug('-'*30)
|
||||
handle(line)
|
||||
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")
|
||||
if db == 'auto':
|
||||
db = pjoin(tmp, "f2b-db.sqlite3")
|
||||
if use_stock and STOCK:
|
||||
# copy config (sub-directories as alias):
|
||||
def ig_dirs(dir, files):
|
||||
|
@ -147,8 +156,7 @@ def _start_params(tmp, use_stock=False, logtarget="/dev/null"):
|
|||
else:
|
||||
# just empty config directory without anything (only fail2ban.conf/jail.conf):
|
||||
os.mkdir(cfg)
|
||||
f = open(pjoin(cfg, "fail2ban.conf"), "w")
|
||||
f.write('\n'.join((
|
||||
_write_file(pjoin(cfg, "fail2ban.conf"), "w",
|
||||
"[Definition]",
|
||||
"loglevel = INFO",
|
||||
"logtarget = " + logtarget,
|
||||
|
@ -156,19 +164,16 @@ def _start_params(tmp, use_stock=False, logtarget="/dev/null"):
|
|||
"socket = " + pjoin(tmp, "f2b.sock"),
|
||||
"pidfile = " + pjoin(tmp, "f2b.pid"),
|
||||
"backend = polling",
|
||||
"dbfile = :memory:",
|
||||
"dbfile = " + db,
|
||||
"dbpurgeage = 1d",
|
||||
"",
|
||||
)))
|
||||
f.close()
|
||||
f = open(pjoin(cfg, "jail.conf"), "w")
|
||||
f.write('\n'.join((
|
||||
)
|
||||
_write_file(pjoin(cfg, "jail.conf"), "w",
|
||||
"[INCLUDES]", "",
|
||||
"[DEFAULT]", "",
|
||||
"",
|
||||
)))
|
||||
f.close()
|
||||
if logSys.level < logging.DEBUG: # if HEAVYDEBUG
|
||||
)
|
||||
if DefLogSys.level < logging.DEBUG: # if HEAVYDEBUG
|
||||
_out_file(pjoin(cfg, "fail2ban.conf"))
|
||||
_out_file(pjoin(cfg, "jail.conf"))
|
||||
# parameters (sock/pid and config, increase verbosity, set log, etc.):
|
||||
|
@ -238,19 +243,78 @@ def with_kill_srv(f):
|
|||
_kill_srv(pidfile)
|
||||
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):
|
||||
|
||||
_orig_exit = Fail2banCmdLine._exit
|
||||
|
||||
def _setLogLevel(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def setUp(self):
|
||||
"""Call before every test case."""
|
||||
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)
|
||||
|
||||
def tearDown(self):
|
||||
"""Call after every test case."""
|
||||
Fail2banCmdLine._exit = self._orig_exit
|
||||
# restore server log target:
|
||||
server.DEF_LOGTARGET = SRV_DEF_LOGTARGET
|
||||
server.DEF_LOGLEVEL = SRV_DEF_LOGLEVEL
|
||||
LogCaptureTestCase.tearDown(self)
|
||||
|
||||
@staticmethod
|
||||
|
@ -304,54 +368,6 @@ class Fail2banClientServerBase(LogCaptureTestCase):
|
|||
phase['end'] = True
|
||||
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()
|
||||
def testStartForeground(self, tmp, startparams):
|
||||
# several commands to server:
|
||||
|
@ -522,6 +538,24 @@ class Fail2banClientTest(Fail2banClientServerBase):
|
|||
self.assertLogged("Usage: ")
|
||||
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):
|
||||
sleeptime = 0.035
|
||||
for verbose in (2, 0):
|
||||
|
@ -626,3 +660,242 @@ class Fail2banServerTest(Fail2banClientServerBase):
|
|||
# again:
|
||||
self.assertTrue(_kill_srv(tmp))
|
||||
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()
|
||||
|
|
|
@ -240,7 +240,7 @@ class Transmitter(TransmitterBase):
|
|||
self.transm.proceed(["add", self.jailName, "polling"])[0], 1)
|
||||
# All name is reserved
|
||||
self.assertEqual(
|
||||
self.transm.proceed(["add", "all", "polling"])[0], 1)
|
||||
self.transm.proceed(["add", "--all", "polling"])[0], 1)
|
||||
|
||||
def testStartStopJail(self):
|
||||
self.assertEqual(
|
||||
|
@ -267,7 +267,7 @@ class Transmitter(TransmitterBase):
|
|||
self.assertTrue( Utils.wait_for(
|
||||
lambda: self.server.isAlive(2) and not isinstance(self.transm.proceed(["status", self.jailName]), RuntimeError),
|
||||
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.assertNotIn(self.jailName, self.server._Server__jails)
|
||||
self.assertNotIn("TestJail2", self.server._Server__jails)
|
||||
|
|
|
@ -119,6 +119,7 @@ def getOptParser(doc=""):
|
|||
|
||||
def initProcess(opts):
|
||||
# Logger:
|
||||
global logSys
|
||||
logSys = getLogger("fail2ban")
|
||||
|
||||
# Numerical level of verbosity corresponding to a log "level"
|
||||
|
@ -577,7 +578,8 @@ class LogCaptureTestCase(unittest.TestCase):
|
|||
print("")
|
||||
logSys.handlers += self._old_handlers
|
||||
logSys.debug('='*10 + ' %s ' + '='*20, self.id())
|
||||
logSys.setLevel(logging.DEBUG)
|
||||
else:
|
||||
logSys.setLevel(logging.DEBUG)
|
||||
|
||||
def tearDown(self):
|
||||
"""Call after every test case."""
|
||||
|
@ -638,8 +640,10 @@ class LogCaptureTestCase(unittest.TestCase):
|
|||
if s_ in logged: # pragma: no cover
|
||||
self.fail("%r was found in the log: ===\n%s===" % (s_, logged))
|
||||
|
||||
def pruneLog(self):
|
||||
def pruneLog(self, logphase=None):
|
||||
self._log.truncate(0)
|
||||
if logphase:
|
||||
logSys.debug('='*5 + ' %s ' + '='*5, logphase)
|
||||
|
||||
def getLog(self):
|
||||
return self._log.getvalue()
|
||||
|
|
Loading…
Reference in New Issue