- Added wildcards support for "logpath"

- Added "set <jail> addlogpath <path>" and "set <jail> dellogpath <path>"
- Adapted pyunit test

git-svn-id: https://fail2ban.svn.sourceforge.net/svnroot/fail2ban/trunk@354 a942ae1a-1317-0410-a47c-b1dcaea8d605
0.x
Cyril Jaquier 2006-09-13 21:31:22 +00:00
parent 96297b7cf3
commit e146d07394
9 changed files with 120 additions and 65 deletions

View File

@ -10,6 +10,7 @@ Fail2Ban (version 0.7.3) 2006/??/??
ver. 0.7.3 (2006/??/??) - beta ver. 0.7.3 (2006/??/??) - beta
---------- ----------
- Added man pages. Thanks to Yaroslav Halchenko - Added man pages. Thanks to Yaroslav Halchenko
- Added wildcard support for "logpath"
ver. 0.7.2 (2006/09/10) - beta ver. 0.7.2 (2006/09/10) - beta
---------- ----------

3
TODO
View File

@ -92,10 +92,11 @@ Legend:
* remove most of the command lines options if possible. * remove most of the command lines options if possible.
- add the possibility to specify wildcard in log files. * add the possibility to specify wildcard in log files.
Example: logfile = /var/log/apache2/access-*.log Example: logfile = /var/log/apache2/access-*.log
Should we start one thread per file or just one thread per Should we start one thread per file or just one thread per
serivce? serivce?
We use just one thread per service
# autodetect date format in log file. Match the most popular # autodetect date format in log file. Match the most popular
format and sort them using the hit ratio. Should avoid format and sort them using the hit ratio. Should avoid

View File

@ -50,10 +50,11 @@ class Beautifier:
def beautify(self, response): def beautify(self, response):
logSys.debug("Beautify " + `response` + " with " + `self.inputCmd`) logSys.debug("Beautify " + `response` + " with " + `self.inputCmd`)
inC = self.inputCmd
msg = response msg = response
if self.inputCmd[0:1] == ['status']: if inC[0:1] == ['status']:
if len(self.inputCmd) > 1: if len(inC) > 1:
msg = "Status for the jail: " + self.inputCmd[1] + "\n" msg = "Status for the jail: " + inC[1] + "\n"
msg = msg + "|- " + response[0][0] + "\n" msg = msg + "|- " + response[0][0] + "\n"
msg = msg + "| |- " + response[0][1][0][0] + ":\t\t" + `response[0][1][0][1]` + "\n" msg = msg + "| |- " + response[0][1][0][0] + ":\t\t" + `response[0][1][0][1]` + "\n"
msg = msg + "| `- " + response[0][1][1][0] + ":\t\t" + `response[0][1][1][1]` + "\n" msg = msg + "| `- " + response[0][1][1][0] + ":\t\t" + `response[0][1][1][1]` + "\n"
@ -64,7 +65,7 @@ class Beautifier:
msg = "Status\n" msg = "Status\n"
msg = msg + "|- " + response[0][0] + ":\t" + `response[0][1]` + "\n" msg = msg + "|- " + response[0][0] + ":\t" + `response[0][1]` + "\n"
msg = msg + "`- " + response[1][0] + ":\t\t" + response[1][1] msg = msg + "`- " + response[1][0] + ":\t\t" + response[1][1]
elif self.inputCmd[1:2] == ['loglevel']: elif inC[1:2] == ['loglevel']:
msg = "Current logging level is " msg = "Current logging level is "
if response == 1: if response == 1:
msg = msg + "ERROR" msg = msg + "ERROR"
@ -76,9 +77,14 @@ class Beautifier:
msg = msg + "DEBUG" msg = msg + "DEBUG"
else: else:
msg = msg + `response` msg = msg + `response`
elif self.inputCmd == ["stop"]: elif inC == ["stop"]:
if response == None: if response == None:
msg = "Shutdown successful" msg = "Shutdown successful"
elif inC[2] == "logpath" or inC[2] == "addlogpath" or inC[2] == "dellogpath":
msg = "Current monitored log file(s):\n"
for path in response[:-1]:
msg = msg + "|- " + path + "\n"
msg = msg + "`- " + response[len(response)-1]
return msg return msg
def beautifyError(self, response): def beautifyError(self, response):

View File

@ -24,7 +24,8 @@ __date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import logging, re import logging, re, glob
from configreader import ConfigReader from configreader import ConfigReader
from filterreader import FilterReader from filterreader import FilterReader
from actionreader import ActionReader from actionreader import ActionReader
@ -95,7 +96,11 @@ class JailReader(ConfigReader):
stream = [["add", self.name]] stream = [["add", self.name]]
for opt in self.opts: for opt in self.opts:
if opt == "logpath": if opt == "logpath":
stream.append(["set", self.name, "logpath", self.opts[opt]]) pathList = glob.glob(self.opts[opt])
if len(pathList) == 0:
logSys.error("No file found for " + self.opts[opt])
for path in pathList:
stream.append(["set", self.name, "addlogpath", path])
elif opt == "maxretry": elif opt == "maxretry":
stream.append(["set", self.name, "maxretry", self.opts[opt]]) stream.append(["set", self.name, "maxretry", self.opts[opt]])
elif opt == "maxtime": elif opt == "maxtime":

View File

@ -128,7 +128,6 @@ class Actions(JailThread):
# @return True if an IP address get banned # @return True if an IP address get banned
def checkBan(self): def checkBan(self):
logSys.debug("Check for IP address to ban")
ticket = self.jail.getFailTicket() ticket = self.jail.getFailTicket()
if ticket != False: if ticket != False:
aInfo = dict() aInfo = dict()
@ -148,7 +147,6 @@ class Actions(JailThread):
# Unban IP address which are outdated. # Unban IP address which are outdated.
def checkUnBan(self): def checkUnBan(self):
logSys.debug("Check for IP address to unban")
for ticket in self.banManager.unBanList(time.time()): for ticket in self.banManager.unBanList(time.time()):
aInfo = dict() aInfo = dict()
aInfo["ip"] = ticket.getIP() aInfo["ip"] = ticket.getIP()

View File

@ -57,9 +57,10 @@ class Filter(JailThread):
## The failures manager. ## The failures manager.
self.failManager = FailManager() self.failManager = FailManager()
## The log file handler. ## The log file handler.
self.fileHandler = None self.crtHandler = None
self.crtFilename = None
## The log file path. ## The log file path.
self.logPath = '' self.logPath = []
## The regular expression matching the failure. ## The regular expression matching the failure.
self.failRegex = '' self.failRegex = ''
self.failRegexObj = None self.failRegexObj = None
@ -68,26 +69,49 @@ class Filter(JailThread):
## The ignore IP list. ## The ignore IP list.
self.ignoreIpList = [] self.ignoreIpList = []
## The time of the last modification of the file. ## The time of the last modification of the file.
self.lastModTime = 0 self.lastModTime = dict()
## The last position of the file. ## The last position of the file.
self.lastPos = 0 self.lastPos = dict()
## The last date in tht log file. ## The last date in tht log file.
self.lastDate = 0 self.lastDate = dict()
## The file statistics. self.file404Cnt = dict()
self.logStats = None
self.dateDetector = DateDetector() self.dateDetector = DateDetector()
self.dateDetector.addDefaultTemplate() self.dateDetector.addDefaultTemplate()
self.fileNotFoundCnt = 0
logSys.info("Created Filter") logSys.info("Created Filter")
## ##
# Set the log file path # Add a log file path
# #
# @param value log file path # @param path log file path
def setLogPath(self, value): def addLogPath(self, path):
self.logPath = value try:
logSys.info("Set logfile = %s" % value) self.logPath.index(path)
logSys.error(path + " already exists")
except ValueError:
self.logPath.append(path)
# Initialize default values
self.lastDate[path] = 0
self.lastModTime[path] = 0
self.lastPos[path] = 0
self.file404Cnt[path] = 0
logSys.info("Added logfile = %s" % path)
##
# Delete a log path
#
# @param path the log file to delete
def delLogPath(self, path):
try:
self.logPath.remove(path)
del self.lastDate[path]
del self.lastModTime[path]
del self.lastPos[path]
logSys.info("Removed logfile = %s" % path)
except ValueError:
logSys.error(path + " is not monitored")
## ##
# Get the log file path # Get the log file path
@ -213,11 +237,16 @@ class Filter(JailThread):
def run(self): def run(self):
self.setActive(True) self.setActive(True)
prevModified = False
while self.isActive(): while self.isActive():
if not self.isIdle: if not self.isIdle:
if self.isModified(): if prevModified:
self.getFailures()
self.dateDetector.sortTemplate() self.dateDetector.sortTemplate()
prevModified = False
for file in self.logPath:
if self.isModified(file):
self.getFailures(file)
prevModified = True
try: try:
ticket = self.failManager.toBan() ticket = self.failManager.toBan()
self.jail.putFailTicket(ticket) self.jail.putFailTicket(ticket)
@ -267,19 +296,21 @@ class Filter(JailThread):
## ##
# Open the log file. # Open the log file.
def openLogFile(self): def openLogFile(self, filename):
""" Opens the log file specified on init. """ Opens the log file specified on init.
""" """
try: try:
self.fileHandler = open(self.logPath) self.crtFilename = filename
self.crtHandler = open(filename)
except OSError: except OSError:
logSys.error("Unable to open "+self.logPath) logSys.error("Unable to open " + filename)
## ##
# Close the log file. # Close the log file.
def closeLogFile(self): def closeLogFile(self):
self.fileHandler.close() self.crtFilename = None
self.crtHandler.close()
## ##
# Checks if the log file has been modified. # Checks if the log file has been modified.
@ -287,23 +318,23 @@ class Filter(JailThread):
# Checks if the log file has been modified using os.stat(). # Checks if the log file has been modified using os.stat().
# @return True if log file has been modified # @return True if log file has been modified
def isModified(self): def isModified(self, filename):
try: try:
self.logStats = os.stat(self.logPath) logStats = os.stat(filename)
self.fileNotFoundCnt = 0 self.file404Cnt[filename] = 0
if self.lastModTime == self.logStats.st_mtime: if self.lastModTime[filename] == logStats.st_mtime:
return False return False
else: else:
logSys.debug(self.logPath + " has been modified") logSys.debug(filename + " has been modified")
self.lastModTime = self.logStats.st_mtime self.lastModTime[filename] = logStats.st_mtime
return True return True
except OSError: except OSError:
logSys.error("Unable to get stat on " + self.logPath) logSys.error("Unable to get stat on " + filename)
self.fileNotFoundCnt = self.fileNotFoundCnt + 1 self.file404Cnt[filename] = self.file404Cnt[filename] + 1
if self.fileNotFoundCnt > 2: if self.file404Cnt[filename] > 2:
logSys.warn("Too much read error. Set the jail idle") logSys.warn("Too much read error. Set the jail idle")
self.jail.setIdle(True) self.jail.setIdle(True)
self.fileNotFoundCnt = 0 self.file404Cnt[filename] = 0
return False return False
## ##
@ -314,22 +345,23 @@ class Filter(JailThread):
# timestamp in order to detect this. # timestamp in order to detect this.
def setFilePos(self): def setFilePos(self):
line = self.fileHandler.readline() line = self.crtHandler.readline()
if self.lastDate < self.dateDetector.getTime(line): lastDate = self.lastDate[self.crtFilename]
logSys.debug("Date " + `self.lastDate` + " is " + "smaller than " + lineDate = self.dateDetector.getUnixTime(line)
`self.dateDetector.getTime(line)`) if lastDate < lineDate:
logSys.debug("Log rotation detected for " + self.logPath) logSys.debug("Date " + `lastDate` + " is smaller than " + `lineDate`)
self.lastPos = 0 logSys.debug("Log rotation detected for " + self.crtFilename)
self.lastPos[self.crtFilename] = 0
logSys.debug("Setting file position to " + `self.lastPos` + " for " + lastPos = self.lastPos[self.crtFilename]
self.logPath) logSys.debug("Setting file position to " + `lastPos` + " for " +
self.fileHandler.seek(self.lastPos) self.crtFilename)
self.crtHandler.seek(lastPos)
## ##
# Get the file position. # Get the file position.
def getFilePos(self): def getFilePos(self):
return self.fileHandler.tell() return self.crtHandler.tell()
## ##
# Gets all the failure in the log file. # Gets all the failure in the log file.
@ -338,18 +370,20 @@ class Filter(JailThread):
# time.time()-self.findTime. When a failure is detected, a FailTicket # time.time()-self.findTime. When a failure is detected, a FailTicket
# is created and is added to the FailManager. # is created and is added to the FailManager.
def getFailures(self): def getFailures(self, filename):
ipList = dict() ipList = dict()
logSys.debug(self.logPath) logSys.debug(filename)
self.openLogFile() self.openLogFile(filename)
self.setFilePos() self.setFilePos()
lastLine = None lastLine = None
for line in self.fileHandler: for line in self.crtHandler:
try: try:
# Try to convert UTF-8 string to Latin-1 # Try to convert UTF-8 string to Latin-1
line = line.decode('utf-8').encode('latin-1') line = line.decode('utf-8').encode('latin-1')
except UnicodeDecodeError: except UnicodeDecodeError:
pass pass
except UnicodeEncodeError:
logSys.warn("Mmhh... Are you sure you watch the correct file?")
if not self.dateDetector.matchTime(line): if not self.dateDetector.matchTime(line):
# There is no valid time in this line # There is no valid time in this line
continue continue
@ -364,9 +398,9 @@ class Filter(JailThread):
continue continue
logSys.debug("Found "+ip) logSys.debug("Found "+ip)
self.failManager.addFailure(FailTicket(ip, unixTime)) self.failManager.addFailure(FailTicket(ip, unixTime))
self.lastPos = self.getFilePos() self.lastPos[filename] = self.getFilePos()
if lastLine: if lastLine:
self.lastDate = self.dateDetector.getTime(lastLine) self.lastDate[filename] = self.dateDetector.getTime(lastLine)
self.closeLogFile() self.closeLogFile()
## ##

View File

@ -123,9 +123,13 @@ class Server:
raise ServerUnknownJail(name) raise ServerUnknownJail(name)
# Filter # Filter
def setLogPath(self, name, file): def addLogPath(self, name, file):
if self.jails.has_key(name): if self.jails.has_key(name):
self.jails[name].getFilter().setLogPath(file) self.jails[name].getFilter().addLogPath(file)
def delLogPath(self, name, file):
if self.jails.has_key(name):
self.jails[name].getFilter().delLogPath(file)
def getLogPath(self, name): def getLogPath(self, name):
return self.getFilter(name).getLogPath() return self.getFilter(name).getLogPath()

View File

@ -129,9 +129,14 @@ class Transmitter:
self.server.setIdleJail(name, False) self.server.setIdleJail(name, False)
return self.server.getIdleJail(name) return self.server.getIdleJail(name)
# Filter # Filter
elif action[1] == "logpath": elif action[1] == "addlogpath":
value = action[2:]
for path in value:
self.server.addLogPath(name, path)
return self.server.getLogPath(name)
elif action[1] == "dellogpath":
value = action[2] value = action[2]
self.server.setLogPath(name, value) self.server.delLogPath(name, value)
return self.server.getLogPath(name) return self.server.getLogPath(name)
elif action[1] == "timeregex": elif action[1] == "timeregex":
value = action[2] value = action[2]

View File

@ -52,20 +52,21 @@ class IgnoreIP(unittest.TestCase):
class LogFile(unittest.TestCase): class LogFile(unittest.TestCase):
filename = "testcases/files/testcase01.log"
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.setLogPath("testcases/files/testcase01.log") self.filter.addLogPath(LogFile.filename)
def tearDown(self): def tearDown(self):
"""Call after every test case.""" """Call after every test case."""
def testOpen(self): def testOpen(self):
self.filter.openLogFile() self.filter.openLogFile(LogFile.filename)
def testIsModified(self): def testIsModified(self):
self.filter.openLogFile() self.assertTrue(self.filter.isModified(LogFile.filename))
self.assertTrue(self.filter.isModified())
class GetFailures(unittest.TestCase): class GetFailures(unittest.TestCase):
@ -73,7 +74,7 @@ class GetFailures(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.setLogPath("testcases/files/testcase01.log") self.filter.addLogPath("testcases/files/testcase01.log")
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")
self.filter.setFailRegex("(?:(?:Authentication failure|Failed [-/\w+]+) for(?: [iI](?:llegal|nvalid) user)?|[Ii](?:llegal|nvalid) user|ROOT LOGIN REFUSED) .*(?: from|FROM) (?:::f{4,6}:)?(?P<host>\S*)") self.filter.setFailRegex("(?:(?:Authentication failure|Failed [-/\w+]+) for(?: [iI](?:llegal|nvalid) user)?|[Ii](?:llegal|nvalid) user|ROOT LOGIN REFUSED) .*(?: from|FROM) (?:::f{4,6}:)?(?P<host>\S*)")