RF: Refactor Jail and JailThread

Includes:
    - documentation to new format and use of properties
    - change isActive->is_active as former no longer documented for
      python3, and later introduction and documented in python2.6
    - status formatter in beautifier somewhat more automatically
      formatted; no changes are required for additional status elements
    - JailThread now set to active within `start` method, complimenting
      `stop` method
pull/628/head
Steven Hiscocks 2014-02-23 17:35:36 +00:00
parent 212d05dc0b
commit df8d700d17
19 changed files with 238 additions and 287 deletions

View File

@ -121,7 +121,7 @@ class SMTPAction(ActionBase):
self.matches = matches self.matches = matches
self.message_values = CallingMap( self.message_values = CallingMap(
jailname = self._jail.getName(), # Doesn't change jailname = self._jail.name,
hostname = socket.gethostname, hostname = socket.gethostname,
bantime = self._jail.actions.getBanTime, bantime = self._jail.actions.getBanTime,
) )

View File

@ -67,28 +67,23 @@ class Beautifier:
msg = "logs: " + response msg = "logs: " + response
elif inC[0:1] == ['status']: elif inC[0:1] == ['status']:
if len(inC) > 1: if len(inC) > 1:
# Create IP list
ipList = ""
for ip in response[1][1][2][1]:
ipList += ip + " "
# Creates file list.
fileList = ""
for f in response[0][1][2][1]:
fileList += f + " "
# Display information # Display information
msg = "Status for the jail: " + inC[1] + "\n" msg = ["Status for the jail: %s" % inC[1]]
msg = msg + "|- " + response[0][0] + "\n" for n, res1 in enumerate(response):
msg = msg + "| |- " + response[0][1][2][0] + ":\t" + fileList + "\n" prefix1 = "`-" if n == len(response) - 1 else "|-"
msg = msg + "| |- " + response[0][1][0][0] + ":\t" + `response[0][1][0][1]` + "\n" msg.append("%s %s" % (prefix1, res1[0]))
msg = msg + "| `- " + response[0][1][1][0] + ":\t" + `response[0][1][1][1]` + "\n" prefix1 = " " if n == len(response) - 1 else "| "
msg = msg + "`- " + response[1][0] + "\n" for m, res2 in enumerate(res1[1]):
msg = msg + " |- " + response[1][1][0][0] + ":\t" + `response[1][1][0][1]` + "\n" prefix2 = prefix1 + ("`-" if m == len(res1[1]) - 1 else "|-")
msg = msg + " | `- " + response[1][1][2][0] + ":\t" + ipList + "\n" val = " ".join(res2[1]) if isinstance(res2[1], list) else res2[1]
msg = msg + " `- " + response[1][1][1][0] + ":\t" + `response[1][1][1][1]` msg.append("%s %s:\t%s" % (prefix2, res2[0], val))
else: else:
msg = "Status\n" msg = ["Status"]
msg = msg + "|- " + response[0][0] + ":\t" + `response[0][1]` + "\n" for n, res1 in enumerate(response):
msg = msg + "`- " + response[1][0] + ":\t\t" + response[1][1] prefix1 = "`-" if n == len(response) - 1 else "|-"
val = " ".join(res1[1]) if isinstance(res1[1], list) else res1[1]
msg.append("%s %s:\t%s" % (prefix1, res1[0], val))
msg = "\n".join(msg)
elif inC[1] == "logtarget": elif inC[1] == "logtarget":
msg = "Current logging target is:\n" msg = "Current logging target is:\n"
msg = msg + "`- " + response msg = msg + "`- " + response

View File

@ -192,30 +192,29 @@ class Actions(JailThread, Mapping):
bool bool
True when the thread exits nicely. True when the thread exits nicely.
""" """
self.setActive(True)
for name, action in self._actions.iteritems(): for name, action in self._actions.iteritems():
try: try:
action.start() action.start()
except Exception as e: except Exception as e:
logSys.error("Failed to start jail '%s' action '%s': %s", logSys.error("Failed to start jail '%s' action '%s': %s",
self._jail.getName(), name, e) self._jail.name, name, e)
while self._isActive(): while self.active:
if not self.getIdle(): if not self.idle:
#logSys.debug(self._jail.getName() + ": action") #logSys.debug(self._jail.name + ": action")
ret = self.__checkBan() ret = self.__checkBan()
if not ret: if not ret:
self.__checkUnBan() self.__checkUnBan()
time.sleep(self.getSleepTime()) time.sleep(self.sleeptime)
else: else:
time.sleep(self.getSleepTime()) time.sleep(self.sleeptime)
self.__flushBan() self.__flushBan()
for name, action in self._actions.iteritems(): for name, action in self._actions.iteritems():
try: try:
action.stop() action.stop()
except Exception as e: except Exception as e:
logSys.error("Failed to stop jail '%s' action '%s': %s", logSys.error("Failed to stop jail '%s' action '%s': %s",
self._jail.getName(), name, e) self._jail.name, name, e)
logSys.debug(self._jail.getName() + ": action terminated") logSys.debug(self._jail.name + ": action terminated")
return True return True
def __checkBan(self): def __checkBan(self):
@ -237,31 +236,31 @@ class Actions(JailThread, Mapping):
aInfo["failures"] = bTicket.getAttempt() aInfo["failures"] = bTicket.getAttempt()
aInfo["time"] = bTicket.getTime() aInfo["time"] = bTicket.getTime()
aInfo["matches"] = "\n".join(bTicket.getMatches()) aInfo["matches"] = "\n".join(bTicket.getMatches())
if self._jail.getDatabase() is not None: if self._jail.database is not None:
aInfo["ipmatches"] = lambda: "\n".join( aInfo["ipmatches"] = lambda: "\n".join(
self._jail.getDatabase().getBansMerged( self._jail.database.getBansMerged(
ip=bTicket.getIP()).getMatches()) ip=bTicket.getIP()).getMatches())
aInfo["ipjailmatches"] = lambda: "\n".join( aInfo["ipjailmatches"] = lambda: "\n".join(
self._jail.getDatabase().getBansMerged( self._jail.database.getBansMerged(
ip=bTicket.getIP(), jail=self._jail).getMatches()) ip=bTicket.getIP(), jail=self._jail).getMatches())
aInfo["ipfailures"] = lambda: "\n".join( aInfo["ipfailures"] = lambda: "\n".join(
self._jail.getDatabase().getBansMerged( self._jail.database.getBansMerged(
ip=bTicket.getIP()).getAttempt()) ip=bTicket.getIP()).getAttempt())
aInfo["ipjailfailures"] = lambda: "\n".join( aInfo["ipjailfailures"] = lambda: "\n".join(
self._jail.getDatabase().getBansMerged( self._jail.database.getBansMerged(
ip=bTicket.getIP(), jail=self._jail).getAttempt()) ip=bTicket.getIP(), jail=self._jail).getAttempt())
if self.__banManager.addBanTicket(bTicket): if self.__banManager.addBanTicket(bTicket):
logSys.warning("[%s] Ban %s" % (self._jail.getName(), aInfo["ip"])) logSys.warning("[%s] Ban %s" % (self._jail.name, aInfo["ip"]))
for name, action in self._actions.iteritems(): for name, action in self._actions.iteritems():
try: try:
action.ban(aInfo) action.ban(aInfo)
except Exception as e: except Exception as e:
logSys.error( logSys.error(
"Failed to execute ban jail '%s' action '%s': %s", "Failed to execute ban jail '%s' action '%s': %s",
self._jail.getName(), name, e) self._jail.name, name, e)
return True return True
else: else:
logSys.info("[%s] %s already banned" % (self._jail.getName(), logSys.info("[%s] %s already banned" % (self._jail.name,
aInfo["ip"])) aInfo["ip"]))
return False return False
@ -298,28 +297,20 @@ class Actions(JailThread, Mapping):
aInfo["failures"] = ticket.getAttempt() aInfo["failures"] = ticket.getAttempt()
aInfo["time"] = ticket.getTime() aInfo["time"] = ticket.getTime()
aInfo["matches"] = "".join(ticket.getMatches()) aInfo["matches"] = "".join(ticket.getMatches())
logSys.warning("[%s] Unban %s" % (self._jail.getName(), aInfo["ip"])) logSys.warning("[%s] Unban %s" % (self._jail.name, aInfo["ip"]))
for name, action in self._actions.iteritems(): for name, action in self._actions.iteritems():
try: try:
action.unban(aInfo) action.unban(aInfo)
except Exception as e: except Exception as e:
logSys.error( logSys.error(
"Failed to execute unban jail '%s' action '%s': %s", "Failed to execute unban jail '%s' action '%s': %s",
self._jail.getName(), name, e) self._jail.name, name, e)
@property
def status(self): def status(self):
"""Get the status of the filter. """Status of active bans, and total ban counts.
Get some informations about the filter state such as the total
number of failures.
Returns
-------
list
List of tuple pairs, each containing a description and value
for general status information.
""" """
ret = [("Currently banned", self.__banManager.size()), ret = [("Currently banned", self.__banManager.size()),
("Total banned", self.__banManager.getBanTotal()), ("Total banned", self.__banManager.getBanTotal()),
("IP list", self.__banManager.getBanList())] ("Banned IP list", self.__banManager.getBanList())]
return ret return ret

View File

@ -184,10 +184,10 @@ class Fail2BanDb(object):
def addJail(self, cur, jail): def addJail(self, cur, jail):
cur.execute( cur.execute(
"INSERT OR REPLACE INTO jails(name, enabled) VALUES(?, 1)", "INSERT OR REPLACE INTO jails(name, enabled) VALUES(?, 1)",
(jail.getName(),)) (jail.name,))
def delJail(self, jail): def delJail(self, jail):
return self.delJailName(jail.getName()) return self.delJailName(jail.name)
@commitandrollback @commitandrollback
def delJailName(self, cur, name): def delJailName(self, cur, name):
@ -211,7 +211,7 @@ class Fail2BanDb(object):
cur.execute( cur.execute(
"SELECT firstlinemd5, lastfilepos FROM logs " "SELECT firstlinemd5, lastfilepos FROM logs "
"WHERE jail=? AND path=?", "WHERE jail=? AND path=?",
(jail.getName(), container.getFileName())) (jail.name, container.getFileName()))
try: try:
firstLineMD5, lastLinePos = cur.fetchone() firstLineMD5, lastLinePos = cur.fetchone()
except TypeError: except TypeError:
@ -220,7 +220,7 @@ class Fail2BanDb(object):
cur.execute( cur.execute(
"INSERT OR REPLACE INTO logs(jail, path, firstlinemd5, lastfilepos) " "INSERT OR REPLACE INTO logs(jail, path, firstlinemd5, lastfilepos) "
"VALUES(?, ?, ?, ?)", "VALUES(?, ?, ?, ?)",
(jail.getName(), container.getFileName(), (jail.name, container.getFileName(),
container.getHash(), container.getPos())) container.getHash(), container.getPos()))
if container.getHash() != firstLineMD5: if container.getHash() != firstLineMD5:
lastLinePos = None lastLinePos = None
@ -232,7 +232,7 @@ class Fail2BanDb(object):
queryArgs = [] queryArgs = []
if jail is not None: if jail is not None:
query += " WHERE jail=?" query += " WHERE jail=?"
queryArgs.append(jail.getName()) queryArgs.append(jail.name)
cur.execute(query, queryArgs) cur.execute(query, queryArgs)
return set(row[0] for row in cur.fetchmany()) return set(row[0] for row in cur.fetchmany())
@ -245,7 +245,7 @@ class Fail2BanDb(object):
"UPDATE logs SET firstlinemd5=?, lastfilepos=? " "UPDATE logs SET firstlinemd5=?, lastfilepos=? "
"WHERE jail=? AND path=?", "WHERE jail=? AND path=?",
(container.getHash(), container.getPos(), (container.getHash(), container.getPos(),
jail.getName(), container.getFileName())) jail.name, container.getFileName()))
@commitandrollback @commitandrollback
def addBan(self, cur, jail, ticket): def addBan(self, cur, jail, ticket):
@ -253,7 +253,7 @@ class Fail2BanDb(object):
#TODO: Implement data parts once arbitrary match keys completed #TODO: Implement data parts once arbitrary match keys completed
cur.execute( cur.execute(
"INSERT INTO bans(jail, ip, timeofban, data) VALUES(?, ?, ?, ?)", "INSERT INTO bans(jail, ip, timeofban, data) VALUES(?, ?, ?, ?)",
(jail.getName(), ticket.getIP(), ticket.getTime(), (jail.name, ticket.getIP(), ticket.getTime(),
{"matches": ticket.getMatches(), {"matches": ticket.getMatches(),
"failures": ticket.getAttempt()})) "failures": ticket.getAttempt()}))
@ -264,7 +264,7 @@ class Fail2BanDb(object):
if jail is not None: if jail is not None:
query += " AND jail=?" query += " AND jail=?"
queryArgs.append(jail.getName()) queryArgs.append(jail.name)
if bantime is not None: if bantime is not None:
query += " AND timeofban > ?" query += " AND timeofban > ?"
queryArgs.append(MyTime.time() - bantime) queryArgs.append(MyTime.time() - bantime)
@ -284,7 +284,7 @@ class Fail2BanDb(object):
return tickets return tickets
def getBansMerged(self, ip, jail=None, **kwargs): def getBansMerged(self, ip, jail=None, **kwargs):
cacheKey = ip if jail is None else "%s|%s" % (ip, jail.getName()) cacheKey = ip if jail is None else "%s|%s" % (ip, jail.name)
if cacheKey in self._bansMergedCache: if cacheKey in self._bansMergedCache:
return self._bansMergedCache[cacheKey] return self._bansMergedCache[cacheKey]
matches = [] matches = []

View File

@ -530,15 +530,10 @@ class Filter(JailThread):
logSys.error(e) logSys.error(e)
return failList return failList
@property
##
# Get the status of the filter.
#
# Get some informations about the filter state such as the total
# number of failures.
# @return a list with tuple
def status(self): def status(self):
"""Status of failures detected by filter.
"""
ret = [("Currently failed", self.failManager.size()), ret = [("Currently failed", self.failManager.size()),
("Total failed", self.failManager.getFailTotal())] ("Total failed", self.failManager.getFailTotal())]
return ret return ret
@ -562,7 +557,7 @@ class FileFilter(Filter):
logSys.error(path + " already exists") logSys.error(path + " already exists")
else: else:
container = FileContainer(path, self.getLogEncoding(), tail) container = FileContainer(path, self.getLogEncoding(), tail)
db = self.jail.getDatabase() db = self.jail.database
if db is not None: if db is not None:
lastpos = db.addLog(self.jail, container) lastpos = db.addLog(self.jail, container)
if lastpos and not tail: if lastpos and not tail:
@ -586,7 +581,7 @@ class FileFilter(Filter):
for log in self.__logPath: for log in self.__logPath:
if log.getFileName() == path: if log.getFileName() == path:
self.__logPath.remove(log) self.__logPath.remove(log)
db = self.jail.getDatabase() 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 = %s" % path)
@ -682,18 +677,21 @@ class FileFilter(Filter):
# might occur leading at least to tests failures. # might occur leading at least to tests failures.
while has_content: while has_content:
line = container.readline() line = container.readline()
if not line or not self._isActive(): if not line or not self.active:
# The jail reached the bottom or has been stopped # The jail reached the bottom or has been stopped
break break
self.processLineAndAdd(line) self.processLineAndAdd(line)
container.close() container.close()
db = self.jail.getDatabase() db = self.jail.database
if db is not None: if db is not None:
db.updateLog(self.jail, container) db.updateLog(self.jail, container)
return True return True
@property
def status(self): def status(self):
ret = Filter.status(self) """Status of Filter plus files being monitored.
"""
ret = super(FileFilter, self).status
path = [m.getFileName() for m in self.getLogPath()] path = [m.getFileName() for m in self.getLogPath()]
ret.append(("File list", path)) ret.append(("File list", path))
return ret return ret

View File

@ -109,16 +109,15 @@ class FilterGamin(FileFilter):
# @return True when the thread exits nicely # @return True when the thread exits nicely
def run(self): def run(self):
self.setActive(True)
# Gamin needs a loop to collect and dispatch events # Gamin needs a loop to collect and dispatch events
while self._isActive(): while self.active:
if not self.getIdle(): if not self.idle:
# We cannot block here because we want to be able to # We cannot block here because we want to be able to
# exit. # exit.
if self.monitor.event_pending(): if self.monitor.event_pending():
self.monitor.handle_events() self.monitor.handle_events()
time.sleep(self.getSleepTime()) time.sleep(self.sleeptime)
logSys.debug(self.jail.getName() + ": filter terminated") logSys.debug(self.jail.name + ": filter terminated")
return True return True

View File

@ -82,12 +82,11 @@ class FilterPoll(FileFilter):
# @return True when the thread exits nicely # @return True when the thread exits nicely
def run(self): def run(self):
self.setActive(True) while self.active:
while self._isActive():
if logSys.getEffectiveLevel() <= 6: if logSys.getEffectiveLevel() <= 6:
logSys.log(6, "Woke up idle=%s with %d files monitored", logSys.log(6, "Woke up idle=%s with %d files monitored",
self.getIdle(), len(self.getLogPath())) self.idle, len(self.getLogPath()))
if not self.getIdle(): if not self.idle:
# Get file modification # Get file modification
for container in self.getLogPath(): for container in self.getLogPath():
filename = container.getFileName() filename = container.getFileName()
@ -104,11 +103,11 @@ class FilterPoll(FileFilter):
self.failManager.cleanup(MyTime.time()) self.failManager.cleanup(MyTime.time())
self.dateDetector.sortTemplate() self.dateDetector.sortTemplate()
self.__modified = False self.__modified = False
time.sleep(self.getSleepTime()) time.sleep(self.sleeptime)
else: else:
time.sleep(self.getSleepTime()) time.sleep(self.sleeptime)
logSys.debug( logSys.debug(
(self.jail is not None and self.jail.getName() or "jailless") + (self.jail is not None and self.jail.name or "jailless") +
" filter terminated") " filter terminated")
return True return True
@ -143,7 +142,7 @@ class FilterPoll(FileFilter):
if self.__file404Cnt[filename] > 2: if self.__file404Cnt[filename] > 2:
logSys.warning("Too many errors. Setting the jail idle") logSys.warning("Too many errors. Setting the jail idle")
if self.jail is not None: if self.jail is not None:
self.jail.setIdle(True) self.jail.idle = True
else: else:
logSys.warning("No jail is assigned to %s" % self) logSys.warning("No jail is assigned to %s" % self)
self.__file404Cnt[filename] = 0 self.__file404Cnt[filename] = 0

View File

@ -168,11 +168,10 @@ class FilterPyinotify(FileFilter):
# loop is necessary # loop is necessary
def run(self): def run(self):
self.setActive(True)
self.__notifier = pyinotify.ThreadedNotifier(self.__monitor, self.__notifier = pyinotify.ThreadedNotifier(self.__monitor,
ProcessPyinotify(self)) ProcessPyinotify(self))
self.__notifier.start() self.__notifier.start()
logSys.debug("pyinotifier started for %s.", self.jail.getName()) logSys.debug("pyinotifier started for %s.", self.jail.name)
# TODO: verify that there is nothing really to be done for # TODO: verify that there is nothing really to be done for
# idle jails # idle jails
return True return True

View File

@ -211,7 +211,6 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
# handover to FailManager # handover to FailManager
def run(self): def run(self):
self.setActive(True)
# Seek to now - findtime in journal # Seek to now - findtime in journal
start_time = datetime.datetime.now() - \ start_time = datetime.datetime.now() - \
@ -224,9 +223,9 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
except OSError: except OSError:
pass # Reading failure, so safe to ignore pass # Reading failure, so safe to ignore
while self._isActive(): while self.active:
if not self.getIdle(): if not self.idle:
while self._isActive(): while self.active:
try: try:
logentry = self.__journal.get_next() logentry = self.__journal.get_next()
except OSError: except OSError:
@ -247,20 +246,14 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
except FailManagerEmpty: except FailManagerEmpty:
self.failManager.cleanup(MyTime.time()) self.failManager.cleanup(MyTime.time())
self.__modified = False self.__modified = False
self.__journal.wait(self.getSleepTime()) self.__journal.wait(self.sleeptime)
logSys.debug((self.jail is not None and self.jail.getName() logSys.debug((self.jail is not None and self.jail.name
or "jailless") +" filter terminated") or "jailless") +" filter terminated")
return True return True
## @property
# Get the status of the filter.
#
# Get some informations about the filter state such as the total
# number of failures.
# @return a list with tuple
def status(self): def status(self):
ret = JournalFilter.status(self) ret = super(FilterSystemd, self).status
ret.append(("Journal matches", ret.append(("Journal matches",
[" + ".join(" ".join(match) for match in self.__matches)])) [" + ".join(" ".join(match) for match in self.__matches)]))
return ret return ret

View File

@ -31,6 +31,12 @@ from .actions import Actions
logSys = logging.getLogger(__name__) logSys = logging.getLogger(__name__)
class Jail: class Jail:
"""Fail2Ban jail, which manages a filter and associated actions.
The class handles the initialisation of a filter, and actions. It's
role is then to act as an interface between the filter and actions,
passing bans detected by the filter, for the actions to then act upon.
"""
#Known backends. Each backend should have corresponding __initBackend method #Known backends. Each backend should have corresponding __initBackend method
# yoh: stored in a list instead of a tuple since only # yoh: stored in a list instead of a tuple since only
@ -38,15 +44,32 @@ class Jail:
_BACKENDS = ['pyinotify', 'gamin', 'polling', 'systemd'] _BACKENDS = ['pyinotify', 'gamin', 'polling', 'systemd']
def __init__(self, name, backend = "auto", db=None): def __init__(self, name, backend = "auto", db=None):
"""Initialise a jail, by initalises filter and actions.
Parameters
----------
name : str
Name assigned to the jail.
backend : str
Backend to be used for filter. "auto" will attempt to pick
the most preferred backend method. Default: "auto"
db : Fail2BanDb
Fail2Ban persistent database instance. Default: `None`
"""
self.__db = db self.__db = db
self.setName(name) # 26 based on iptable chain name limit of 30 less len('f2b-')
if len(name) >= 26:
logSys.warning("Jail name %r might be too long and some commands "
"might not function correctly. Please shorten"
% name)
self.__name = name
self.__queue = Queue.Queue() self.__queue = Queue.Queue()
self.__filter = None self.__filter = None
logSys.info("Creating new jail '%s'" % self.__name) logSys.info("Creating new jail '%s'" % self.name)
self._setBackend(backend) self._setBackend(backend)
def __repr__(self): def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.__name) return "%s(%r)" % (self.__class__.__name__, self.name)
def _setBackend(self, backend): def _setBackend(self, backend):
backend = backend.lower() # to assure consistent matching backend = backend.lower() # to assure consistent matching
@ -78,51 +101,49 @@ class Jail:
"Backend %r failed to initialize due to %s" % (b, e)) "Backend %r failed to initialize due to %s" % (b, e))
# log error since runtime error message isn't printed, INVALID COMMAND # log error since runtime error message isn't printed, INVALID COMMAND
logSys.error( logSys.error(
"Failed to initialize any backend for Jail %r" % self.__name) "Failed to initialize any backend for Jail %r" % self.name)
raise RuntimeError( raise RuntimeError(
"Failed to initialize any backend for Jail %r" % self.__name) "Failed to initialize any backend for Jail %r" % self.name)
def _initPolling(self): def _initPolling(self):
logSys.info("Jail '%s' uses poller" % self.__name) logSys.info("Jail '%s' uses poller" % self.name)
from filterpoll import FilterPoll from filterpoll import FilterPoll
self.__filter = FilterPoll(self) self.__filter = FilterPoll(self)
def _initGamin(self): def _initGamin(self):
# Try to import gamin # Try to import gamin
import gamin import gamin
logSys.info("Jail '%s' uses Gamin" % self.__name) logSys.info("Jail '%s' uses Gamin" % self.name)
from filtergamin import FilterGamin from filtergamin import FilterGamin
self.__filter = FilterGamin(self) self.__filter = FilterGamin(self)
def _initPyinotify(self): def _initPyinotify(self):
# Try to import pyinotify # Try to import pyinotify
import pyinotify import pyinotify
logSys.info("Jail '%s' uses pyinotify" % self.__name) logSys.info("Jail '%s' uses pyinotify" % self.name)
from filterpyinotify import FilterPyinotify from filterpyinotify import FilterPyinotify
self.__filter = FilterPyinotify(self) self.__filter = FilterPyinotify(self)
def _initSystemd(self): # pragma: systemd no cover def _initSystemd(self): # pragma: systemd no cover
# Try to import systemd # Try to import systemd
import systemd import systemd
logSys.info("Jail '%s' uses systemd" % self.__name) logSys.info("Jail '%s' uses systemd" % self.name)
from filtersystemd import FilterSystemd from filtersystemd import FilterSystemd
self.__filter = FilterSystemd(self) self.__filter = FilterSystemd(self)
def setName(self, name): @property
# 26 based on iptable chain name limit of 30 less len('f2b-') def name(self):
if len(name) >= 26: """Name of jail.
logSys.warning("Jail name %r might be too long and some commands " """
"might not function correctly. Please shorten"
% name)
self.__name = name
def getName(self):
return self.__name return self.__name
def getDatabase(self): @property
def database(self):
"""The database used to store persistent data for the jail.
"""
return self.__db return self.__db
@property @property
def filter(self): def filter(self):
"""The filter which the jail is using to monitor log files. """The filter which the jail is using to monitor log files.
@ -134,50 +155,71 @@ class Jail:
"""Actions object used to manage actions for jail. """Actions object used to manage actions for jail.
""" """
return self.__actions return self.__actions
@property
def idle(self):
"""A boolean indicating whether jail is idle.
"""
return self.filter.idle or self.actions.idle
@idle.setter
def idle(self, value):
self.filter.idle = value
self.actions.idle = value
@property
def status(self):
"""The status of the jail.
"""
return [
("Filter", self.filter.status),
("Actions", self.actions.status),
]
def putFailTicket(self, ticket): def putFailTicket(self, ticket):
"""Add a fail ticket to the jail.
Used by filter to add a failure for banning.
"""
self.__queue.put(ticket) self.__queue.put(ticket)
if self.__db is not None: if self.database is not None:
self.__db.addBan(self, ticket) self.database.addBan(self, ticket)
def getFailTicket(self): def getFailTicket(self):
"""Get a fail ticket from the jail.
Used by actions to get a failure for banning.
"""
try: try:
return self.__queue.get(False) return self.__queue.get(False)
except Queue.Empty: except Queue.Empty:
return False return False
def start(self): def start(self):
self.__filter.start() """Start the jail, by starting filter and actions threads.
self.__actions.start()
Once stated, also queries the persistent database to reinstate
any valid bans.
"""
self.filter.start()
self.actions.start()
# Restore any previous valid bans from the database # Restore any previous valid bans from the database
if self.__db is not None: if self.database is not None:
for ticket in self.__db.getBans( for ticket in self.database.getBans(
jail=self, bantime=self.__actions.getBanTime()): jail=self, bantime=self.actions.getBanTime()):
self.__queue.put(ticket) self.__queue.put(ticket)
logSys.info("Jail '%s' started" % self.__name) logSys.info("Jail '%s' started" % self.name)
def stop(self): def stop(self):
self.__filter.stop() """Stop the jail, by stopping filter and actions threads.
self.__actions.stop() """
self.__filter.join() self.filter.stop()
self.__actions.join() self.actions.stop()
logSys.info("Jail '%s' stopped" % self.__name) self.filter.join()
self.actions.join()
def isAlive(self): logSys.info("Jail '%s' stopped" % self.name)
isAlive0 = self.__filter.isAlive()
isAlive1 = self.__actions.isAlive() def is_alive(self):
return isAlive0 or isAlive1 """Check jail "is_alive" by checking filter and actions threads.
"""
def setIdle(self, value): return self.filter.is_alive() or self.actions.is_alive()
self.__filter.setIdle(value)
self.__actions.setIdle(value)
def getIdle(self):
return self.__filter.getIdle() or self.__actions.getIdle()
def getStatus(self):
fStatus = self.__filter.status()
aStatus = self.__actions.status()
ret = [("filter", fStatus),
("action", aStatus)]
return ret

View File

@ -25,94 +25,42 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
from threading import Thread from threading import Thread
import logging from abc import abstractproperty, abstractmethod
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)
class JailThread(Thread): class JailThread(Thread):
"""Abstract class for threading elements in Fail2Ban.
## """
# Constructor.
#
# Initialize the filter object with default values.
# @param jail the jail object
def __init__(self): def __init__(self):
Thread.__init__(self) """Initialise a JailThread instance.
"""
super(JailThread, self).__init__()
## Control the state of the thread. ## Control the state of the thread.
self.__isRunning = False self.active = False
## Control the idle state of the thread. ## Control the idle state of the thread.
self.__isIdle = False self.idle = False
## The time the thread sleeps in the loop. ## The time the thread sleeps in the loop.
self.__sleepTime = 1 self.sleeptime = 1
## @abstractproperty
# Set the time that the thread sleeps. def status(self): # pragma: no cover - abstract
# """Abstract - Should provide status information.
# This value could also be called "polling time". A value of 1 is a """
# good one. This unit is "second" pass
# @param value the polling time (second)
def start(self):
def setSleepTime(self, value): """Sets active flag and starts thread.
self.__sleepTime = value """
logSys.info("Set sleeptime %s" % value) self.active = True
super(JailThread, self).start()
##
# Get the time that the thread sleeps. def stop(self):
# """Sets `active` property to False, to flag run method to return.
# @return the polling time """
self.active = False
def getSleepTime(self):
return self.__sleepTime @abstractmethod
def run(self): # pragma: no cover - absract
## """Abstract - Called when thread starts, thread stops when returns.
# Set the idle flag. """
#
# This flag stops the check of the log file.
# @param value boolean value
def setIdle(self, value):
self.__isIdle = value
##
# Get the idle state.
#
# @return the idle state
def getIdle(self):
return self.__isIdle
##
# Stop the thread.
#
# Stop the exection of the thread and quit.
def stop(self):
self.__isRunning = False
##
# Set the isRunning flag.
#
# @param value True if the thread is running
def setActive(self, value):
self.__isRunning = value
##
# Check if the thread is active.
#
# Check if the filter thread is running.
# @return True if the thread is running
def _isActive(self):
return self.__isRunning
##
# Get the status of the thread
#
# Get some informations about the thread. This is an abstract method.
# @return a list with tuple
def status(self):
pass pass

View File

@ -132,7 +132,7 @@ class Server:
def startJail(self, name): def startJail(self, name):
try: try:
self.__lock.acquire() self.__lock.acquire()
if not self.isAlive(name): if not self.__jails[name].is_alive():
self.__jails[name].start() self.__jails[name].start()
finally: finally:
self.__lock.release() self.__lock.release()
@ -141,7 +141,7 @@ class Server:
logSys.debug("Stopping jail %s" % name) logSys.debug("Stopping jail %s" % name)
try: try:
self.__lock.acquire() self.__lock.acquire()
if self.isAlive(name): if self.__jails[name].is_alive():
self.__jails[name].stop() self.__jails[name].stop()
self.delJail(name) self.delJail(name)
finally: finally:
@ -155,16 +155,13 @@ class Server:
self.stopJail(jail) self.stopJail(jail)
finally: finally:
self.__lock.release() self.__lock.release()
def isAlive(self, name):
return self.__jails[name].isAlive()
def setIdleJail(self, name, value): def setIdleJail(self, name, value):
self.__jails[name].setIdle(value) self.__jails[name].idle = value
return True return True
def getIdleJail(self, name): def getIdleJail(self, name):
return self.__jails[name].getIdle() return self.__jails[name].idle
# Filter # Filter
def addIgnoreIP(self, name, ip): def addIgnoreIP(self, name, ip):
@ -316,7 +313,7 @@ class Server:
self.__lock.release() self.__lock.release()
def statusJail(self, name): def statusJail(self, name):
return self.__jails[name].getStatus() return self.__jails[name].status
# Logging # Logging

View File

@ -77,7 +77,7 @@ class SMTPActionTest(unittest.TestCase):
self.assertEqual(self.smtpd.mailfrom, "fail2ban") self.assertEqual(self.smtpd.mailfrom, "fail2ban")
self.assertEqual(self.smtpd.rcpttos, ["root"]) self.assertEqual(self.smtpd.rcpttos, ["root"])
self.assertTrue( self.assertTrue(
"Subject: [Fail2Ban] %s: started" % self.jail.getName() "Subject: [Fail2Ban] %s: started" % self.jail.name
in self.smtpd.data) in self.smtpd.data)
def testStop(self): def testStop(self):
@ -86,7 +86,7 @@ class SMTPActionTest(unittest.TestCase):
self.assertEqual(self.smtpd.rcpttos, ["root"]) self.assertEqual(self.smtpd.rcpttos, ["root"])
self.assertTrue( self.assertTrue(
"Subject: [Fail2Ban] %s: stopped" % "Subject: [Fail2Ban] %s: stopped" %
self.jail.getName() in self.smtpd.data) self.jail.name in self.smtpd.data)
def testBan(self): def testBan(self):
aInfo = { aInfo = {
@ -102,7 +102,7 @@ class SMTPActionTest(unittest.TestCase):
self.assertEqual(self.smtpd.rcpttos, ["root"]) self.assertEqual(self.smtpd.rcpttos, ["root"])
self.assertTrue( self.assertTrue(
"Subject: [Fail2Ban] %s: banned %s" % "Subject: [Fail2Ban] %s: banned %s" %
(self.jail.getName(), aInfo['ip']) in self.smtpd.data) (self.jail.name, aInfo['ip']) in self.smtpd.data)
self.assertTrue( self.assertTrue(
"%i attempts" % aInfo['failures'] in self.smtpd.data) "%i attempts" % aInfo['failures'] in self.smtpd.data)

View File

@ -84,8 +84,8 @@ class ExecuteActions(LogCaptureTestCase):
self.__actions.stop() self.__actions.stop()
self.__actions.join() self.__actions.join()
self.assertEqual(self.__actions.status(),[("Currently banned", 0 ), self.assertEqual(self.__actions.status,[("Currently banned", 0 ),
("Total banned", 0 ), ("IP list", [] )]) ("Total banned", 0 ), ("Banned IP list", [] )])
def testAddActionPython(self): def testAddActionPython(self):

View File

@ -61,7 +61,7 @@ class DatabaseTest(unittest.TestCase):
self.db = Fail2BanDb(self.dbFilename) self.db = Fail2BanDb(self.dbFilename)
# and check jail of same name still present # and check jail of same name still present
self.assertTrue( self.assertTrue(
self.jail.getName() in self.db.getJailNames(), self.jail.name in self.db.getJailNames(),
"Jail not retained in Db after disconnect reconnect.") "Jail not retained in Db after disconnect reconnect.")
def testUpdateDb(self): def testUpdateDb(self):
@ -80,7 +80,7 @@ class DatabaseTest(unittest.TestCase):
self.jail = DummyJail() self.jail = DummyJail()
self.db.addJail(self.jail) self.db.addJail(self.jail)
self.assertTrue( self.assertTrue(
self.jail.getName() in self.db.getJailNames(), self.jail.name in self.db.getJailNames(),
"Jail not added to database") "Jail not added to database")
def testAddLog(self): def testAddLog(self):

View File

@ -32,6 +32,8 @@ class DummyJail(object):
def __init__(self): def __init__(self):
self.lock = Lock() self.lock = Lock()
self.queue = [] self.queue = []
self.idle = False
self.database = None
self.actions = Actions(self) self.actions = Actions(self)
def __len__(self): def __len__(self):
@ -58,15 +60,6 @@ class DummyJail(object):
finally: finally:
self.lock.release() self.lock.release()
def setIdle(self, value): @property
pass def name(self):
def getIdle(self):
pass
def getName(self):
return "DummyJail #%s with %d tickets" % (id(self), len(self)) return "DummyJail #%s with %d tickets" % (id(self), len(self))
def getDatabase(self):
return None

View File

@ -325,7 +325,7 @@ class LogFileMonitor(LogCaptureTestCase):
self.file = open(self.name, 'a') self.file = open(self.name, 'a')
self.filter = FilterPoll(DummyJail()) self.filter = FilterPoll(DummyJail())
self.filter.addLogPath(self.name) self.filter.addLogPath(self.name)
self.filter.setActive(True) self.filter.active = True
self.filter.addFailRegex("(?:(?:Authentication failure|Failed [-/\w+]+) for(?: [iI](?:llegal|nvalid) user)?|[Ii](?:llegal|nvalid) user|ROOT LOGIN REFUSED) .*(?: from|FROM) <HOST>") self.filter.addFailRegex("(?:(?:Authentication failure|Failed [-/\w+]+) for(?: [iI](?:llegal|nvalid) user)?|[Ii](?:llegal|nvalid) user|ROOT LOGIN REFUSED) .*(?: from|FROM) <HOST>")
def tearDown(self): def tearDown(self):
@ -466,7 +466,7 @@ def get_monitor_failures_testcase(Filter_):
self.jail = DummyJail() self.jail = DummyJail()
self.filter = Filter_(self.jail) self.filter = Filter_(self.jail)
self.filter.addLogPath(self.name) self.filter.addLogPath(self.name)
self.filter.setActive(True) self.filter.active = True
self.filter.addFailRegex("(?:(?:Authentication failure|Failed [-/\w+]+) for(?: [iI](?:llegal|nvalid) user)?|[Ii](?:llegal|nvalid) user|ROOT LOGIN REFUSED) .*(?: from|FROM) <HOST>") self.filter.addFailRegex("(?:(?:Authentication failure|Failed [-/\w+]+) for(?: [iI](?:llegal|nvalid) user)?|[Ii](?:llegal|nvalid) user|ROOT LOGIN REFUSED) .*(?: from|FROM) <HOST>")
self.filter.start() self.filter.start()
# If filter is polling it would sleep a bit to guarantee that # If filter is polling it would sleep a bit to guarantee that
@ -687,7 +687,7 @@ def get_monitor_failures_journal_testcase(Filter_): # pragma: systemd no cover
"TEST_UUID=%s" % self.test_uuid]) "TEST_UUID=%s" % self.test_uuid])
self.journal_fields = { self.journal_fields = {
'TEST_FIELD': "1", 'TEST_UUID': self.test_uuid} 'TEST_FIELD': "1", 'TEST_UUID': self.test_uuid}
self.filter.setActive(True) self.filter.active = True
self.filter.addFailRegex("(?:(?:Authentication failure|Failed [-/\w+]+) for(?: [iI](?:llegal|nvalid) user)?|[Ii](?:llegal|nvalid) user|ROOT LOGIN REFUSED) .*(?: from|FROM) <HOST>") self.filter.addFailRegex("(?:(?:Authentication failure|Failed [-/\w+]+) for(?: [iI](?:llegal|nvalid) user)?|[Ii](?:llegal|nvalid) user|ROOT LOGIN REFUSED) .*(?: from|FROM) <HOST>")
self.filter.start() self.filter.start()
@ -804,7 +804,7 @@ class GetFailures(unittest.TestCase):
setUpMyTime() setUpMyTime()
self.jail = DummyJail() self.jail = DummyJail()
self.filter = FileFilter(self.jail) self.filter = FileFilter(self.jail)
self.filter.setActive(True) self.filter.active = True
# TODO Test this # TODO Test this
#self.filter.setTimeRegex("\S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}") #self.filter.setTimeRegex("\S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}")
#self.filter.setTimePattern("%b %d %H:%M:%S") #self.filter.setTimePattern("%b %d %H:%M:%S")
@ -895,7 +895,7 @@ class GetFailures(unittest.TestCase):
('warn', output_yes)): ('warn', output_yes)):
jail = DummyJail() jail = DummyJail()
filter_ = FileFilter(jail, useDns=useDns) filter_ = FileFilter(jail, useDns=useDns)
filter_.setActive(True) filter_.active = True
filter_.failManager.setMaxRetry(1) # we might have just few failures filter_.failManager.setMaxRetry(1) # we might have just few failures
filter_.addLogPath(GetFailures.FILENAME_USEDNS) filter_.addLogPath(GetFailures.FILENAME_USEDNS)

View File

@ -45,7 +45,7 @@ class FilterSamplesRegex(unittest.TestCase):
def setUp(self): def setUp(self):
"""Call before every test case.""" """Call before every test case."""
self.filter = Filter(None) self.filter = Filter(None)
self.filter.setActive(True) self.filter.active = True
setUpMyTime() setUpMyTime()

View File

@ -227,8 +227,7 @@ class Transmitter(TransmitterBase):
time.sleep(1) time.sleep(1)
self.assertEqual( self.assertEqual(
self.transm.proceed(["stop", self.jailName]), (0, None)) self.transm.proceed(["stop", self.jailName]), (0, None))
self.assertRaises( self.assertTrue(self.jailName not in self.server._Server__jails)
UnknownJailException, self.server.isAlive, self.jailName)
def testStartStopAllJail(self): def testStartStopAllJail(self):
self.server.addJail("TestJail2", "auto") self.server.addJail("TestJail2", "auto")
@ -242,10 +241,8 @@ class Transmitter(TransmitterBase):
time.sleep(0.1) time.sleep(0.1)
self.assertEqual(self.transm.proceed(["stop", "all"]), (0, None)) self.assertEqual(self.transm.proceed(["stop", "all"]), (0, None))
time.sleep(1) time.sleep(1)
self.assertRaises( self.assertTrue(self.jailName not in self.server._Server__jails)
UnknownJailException, self.server.isAlive, self.jailName) self.assertTrue("TestJail2" not in self.server._Server__jails)
self.assertRaises(
UnknownJailException, self.server.isAlive, "TestJail2")
def testJailIdle(self): def testJailIdle(self):
self.assertEqual( self.assertEqual(
@ -482,15 +479,15 @@ class Transmitter(TransmitterBase):
self.assertEqual(self.transm.proceed(["status", self.jailName]), self.assertEqual(self.transm.proceed(["status", self.jailName]),
(0, (0,
[ [
('filter', [ ('Filter', [
('Currently failed', 0), ('Currently failed', 0),
('Total failed', 0), ('Total failed', 0),
('File list', [])] ('File list', [])]
), ),
('action', [ ('Actions', [
('Currently banned', 0), ('Currently banned', 0),
('Total banned', 0), ('Total banned', 0),
('IP list', [])] ('Banned IP list', [])]
) )
] ]
) )
@ -774,7 +771,7 @@ class JailTests(unittest.TestCase):
# Just a smoke test for now # Just a smoke test for now
longname = "veryveryverylongname" longname = "veryveryverylongname"
jail = Jail(longname) jail = Jail(longname)
self.assertEqual(jail.getName(), longname) self.assertEqual(jail.name, longname)
class RegexTests(unittest.TestCase): class RegexTests(unittest.TestCase):