mirror of https://github.com/fail2ban/fail2ban
Merge commit '0.8.6-95-gc0c1232' into debian-devel
* commit '0.8.6-95-gc0c1232': ENH: tests much more robust now across pythons 2.4 -- 2.7 BF+RF: pyinotify refreshes watcher upon CREATE, unified/simplified *(add|del)LogPath among *Filters Ask users to report bugs to github's issues Replace "|" with "_" in ipmasq-ZZZzzz|fail2ban.rul (Closes gh-66)pull/808/head
commit
cbce65574b
|
@ -101,7 +101,7 @@ class Fail2banClient:
|
|||
printFormatted()
|
||||
|
||||
print
|
||||
print "Report bugs to <cyril.jaquier@fail2ban.org>"
|
||||
print "Report bugs to https://github.com/fail2ban/fail2ban/issues"
|
||||
|
||||
def dispInteractive(self):
|
||||
print "Fail2Ban v" + version + " reads log file that contains password failure report"
|
||||
|
|
|
@ -122,7 +122,7 @@ class Fail2banRegex:
|
|||
print " string a string representing an 'ignoreregex'"
|
||||
print " filename path to a filter file (filter.d/sshd.conf)"
|
||||
print
|
||||
print "Report bugs to <cyril.jaquier@fail2ban.org>"
|
||||
print "Report bugs to https://github.com/fail2ban/fail2ban/issues"
|
||||
dispUsage = staticmethod(dispUsage)
|
||||
|
||||
def getCmdLineOptions(self, optList):
|
||||
|
|
|
@ -88,7 +88,7 @@ class Fail2banServer:
|
|||
print " -h, --help display this help message"
|
||||
print " -V, --version print the version"
|
||||
print
|
||||
print "Report bugs to <cyril.jaquier@fail2ban.org>"
|
||||
print "Report bugs to https://github.com/fail2ban/fail2ban/issues"
|
||||
|
||||
def __getCmdLineOptions(self, optList):
|
||||
""" Gets the command line options
|
||||
|
|
|
@ -395,8 +395,19 @@ class FileFilter(Filter):
|
|||
# @param path log file path
|
||||
|
||||
def addLogPath(self, path, tail = False):
|
||||
container = FileContainer(path, tail)
|
||||
self.__logPath.append(container)
|
||||
if self.containsLogPath(path):
|
||||
logSys.error(path + " already exists")
|
||||
else:
|
||||
container = FileContainer(path, tail)
|
||||
self.__logPath.append(container)
|
||||
logSys.info("Added logfile = %s" % path)
|
||||
self._addLogPath(path) # backend specific
|
||||
|
||||
def _addLogPath(self, path):
|
||||
# nothing to do by default
|
||||
# to be overriden by backends
|
||||
pass
|
||||
|
||||
|
||||
##
|
||||
# Delete a log path
|
||||
|
@ -407,8 +418,15 @@ class FileFilter(Filter):
|
|||
for log in self.__logPath:
|
||||
if log.getFileName() == path:
|
||||
self.__logPath.remove(log)
|
||||
logSys.info("Removed logfile = %s" % path)
|
||||
self._delLogPath(path)
|
||||
return
|
||||
|
||||
def _delLogPath(self, path):
|
||||
# nothing to do by default
|
||||
# to be overriden by backends
|
||||
pass
|
||||
|
||||
##
|
||||
# Get the log file path
|
||||
#
|
||||
|
|
|
@ -72,27 +72,17 @@ class FilterGamin(FileFilter):
|
|||
#
|
||||
# @param path log file path
|
||||
|
||||
def addLogPath(self, path, tail = False):
|
||||
if self.containsLogPath(path):
|
||||
logSys.error(path + " already exists")
|
||||
else:
|
||||
self.monitor.watch_file(path, self.callback)
|
||||
FileFilter.addLogPath(self, path, tail)
|
||||
logSys.info("Added logfile = %s" % path)
|
||||
|
||||
def _addLogPath(self, path):
|
||||
self.monitor.watch_file(path, self.callback)
|
||||
|
||||
##
|
||||
# Delete a log path
|
||||
#
|
||||
# @param path the log file to delete
|
||||
|
||||
|
||||
def delLogPath(self, path):
|
||||
if not self.containsLogPath(path):
|
||||
logSys.error(path + " is not monitored")
|
||||
else:
|
||||
self.monitor.stop_watch(path)
|
||||
FileFilter.delLogPath(self, path)
|
||||
logSys.info("Removed logfile = %s" % path)
|
||||
|
||||
self.monitor.stop_watch(path)
|
||||
|
||||
##
|
||||
# Main loop.
|
||||
#
|
||||
|
|
|
@ -63,28 +63,18 @@ class FilterPoll(FileFilter):
|
|||
#
|
||||
# @param path log file path
|
||||
|
||||
def addLogPath(self, path, tail = False):
|
||||
if self.containsLogPath(path):
|
||||
logSys.error(path + " already exists")
|
||||
else:
|
||||
self.__lastModTime[path] = 0
|
||||
self.__file404Cnt[path] = 0
|
||||
FileFilter.addLogPath(self, path, tail)
|
||||
logSys.info("Added logfile = %s" % path)
|
||||
def _addLogPath(self, path):
|
||||
self.__lastModTime[path] = 0
|
||||
self.__file404Cnt[path] = 0
|
||||
|
||||
##
|
||||
# Delete a log path
|
||||
#
|
||||
# @param path the log file to delete
|
||||
|
||||
def delLogPath(self, path):
|
||||
if not self.containsLogPath(path):
|
||||
logSys.error(path + " is not monitored")
|
||||
else:
|
||||
del self.__lastModTime[path]
|
||||
del self.__file404Cnt[path]
|
||||
FileFilter.delLogPath(self, path)
|
||||
logSys.info("Removed logfile = %s" % path)
|
||||
def _delLogPath(self, path):
|
||||
del self.__lastModTime[path]
|
||||
del self.__file404Cnt[path]
|
||||
|
||||
##
|
||||
# Main loop.
|
||||
|
|
|
@ -57,7 +57,29 @@ class FilterPyinotify(FileFilter):
|
|||
logSys.debug("Created FilterPyinotify")
|
||||
|
||||
|
||||
def callback(self, path):
|
||||
def callback(self, event):
|
||||
path = event.pathname
|
||||
if event.mask == pyinotify.IN_CREATE:
|
||||
# check if that is a file we care about
|
||||
if not path in self.__watches:
|
||||
logSys.debug("Ignoring creation of %s we do not monitor" % path)
|
||||
return
|
||||
else:
|
||||
# we need to substitute the watcher with a new one, so first
|
||||
# remove old one
|
||||
self._delFileWatcher(path)
|
||||
# place a new one
|
||||
self._addFileWatcher(path)
|
||||
|
||||
self._process_file(path)
|
||||
|
||||
|
||||
def _process_file(self, path):
|
||||
"""Process a given file
|
||||
|
||||
TODO -- RF:
|
||||
this is a common logic and must be shared/provided by FileFilter
|
||||
"""
|
||||
self.getFailures(path)
|
||||
try:
|
||||
while True:
|
||||
|
@ -68,57 +90,58 @@ class FilterPyinotify(FileFilter):
|
|||
self.dateDetector.sortTemplate()
|
||||
self.__modified = False
|
||||
|
||||
|
||||
def _addFileWatcher(self, path):
|
||||
wd = self.__monitor.add_watch(path, pyinotify.IN_MODIFY)
|
||||
self.__watches.update(wd)
|
||||
logSys.debug("Added file watcher for %s" % path)
|
||||
# process the file since we did get even
|
||||
self._process_file(path)
|
||||
|
||||
|
||||
def _delFileWatcher(self, path):
|
||||
wdInt = self.__watches[path]
|
||||
wd = self.__monitor.rm_watch(wdInt)
|
||||
if wd[wdInt]:
|
||||
del self.__watches[path]
|
||||
logSys.debug("Removed file watcher for %s" % path)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
##
|
||||
# Add a log file path
|
||||
#
|
||||
# @param path log file path
|
||||
|
||||
def addLogPath(self, path, tail=False):
|
||||
if self.containsLogPath(path):
|
||||
logSys.error(path + " already exists")
|
||||
else:
|
||||
wd = self.__monitor.add_watch(path, pyinotify.IN_MODIFY)
|
||||
self.__watches.update(wd)
|
||||
def _addLogPath(self, path):
|
||||
path_dir = dirname(path)
|
||||
if not (path_dir in self.__watches):
|
||||
# we need to watch also the directory for IN_CREATE
|
||||
self.__watches.update(
|
||||
self.__monitor.add_watch(path_dir, pyinotify.IN_CREATE))
|
||||
logSys.debug("Added monitor for the parent directory %s" % path_dir)
|
||||
|
||||
FileFilter.addLogPath(self, path, tail)
|
||||
logSys.info("Added logfile = %s" % path)
|
||||
self._addFileWatcher(path)
|
||||
|
||||
path_dir = dirname(path)
|
||||
if not (path_dir in self.__watches):
|
||||
# we need to watch also the directory for IN_CREATE
|
||||
self.__watches.update(
|
||||
self.__monitor.add_watch(path_dir, pyinotify.IN_CREATE))
|
||||
logSys.debug("Monitor also parent directory %s" % path_dir)
|
||||
|
||||
# sniff the file
|
||||
self.callback(path)
|
||||
|
||||
##
|
||||
# Delete a log path
|
||||
#
|
||||
# @param path the log file to delete
|
||||
|
||||
def delLogPath(self, path):
|
||||
if not self.containsLogPath(path):
|
||||
logSys.error(path + " is not monitored")
|
||||
else:
|
||||
wdInt = self.__watches[path]
|
||||
wd = self.__monitor.rm_watch(wdInt)
|
||||
if wd[wdInt]:
|
||||
del self.__watches[path]
|
||||
FileFilter.delLogPath(self, path)
|
||||
logSys.info("Removed logfile = %s" % path)
|
||||
else:
|
||||
logSys.error("Failed to remove watch on path: %s", path)
|
||||
def _delLogPath(self, path):
|
||||
if not self._delFileWatcher(path):
|
||||
logSys.error("Failed to remove watch on path: %s", path)
|
||||
|
||||
path_dir = dirname(path)
|
||||
if not len([k for k in self.__watches
|
||||
if k.startswith(path_dir + pathsep)]):
|
||||
# Remove watches for the directory
|
||||
# since there is no other monitored file under this directory
|
||||
wdInt = self.__watches.pop(path_dir)
|
||||
_ = self.__monitor.rm_watch(wdInt)
|
||||
logSys.debug("Remove monitor for the parent directory %s" % path_dir)
|
||||
path_dir = dirname(path)
|
||||
if not len([k for k in self.__watches
|
||||
if k.startswith(path_dir + pathsep)]):
|
||||
# Remove watches for the directory
|
||||
# since there is no other monitored file under this directory
|
||||
wdInt = self.__watches.pop(path_dir)
|
||||
_ = self.__monitor.rm_watch(wdInt)
|
||||
logSys.debug("Removed monitor for the parent directory %s" % path_dir)
|
||||
|
||||
|
||||
##
|
||||
|
@ -169,4 +192,4 @@ class ProcessPyinotify(pyinotify.ProcessEvent):
|
|||
# just need default, since using mask on watch to limit events
|
||||
def process_default(self, event):
|
||||
logSys.debug("Callback for Event: %s" % event)
|
||||
self.__FileFilter.callback(event.pathname)
|
||||
self.__FileFilter.callback(event)
|
||||
|
|
|
@ -24,6 +24,7 @@ __license__ = "GPL"
|
|||
|
||||
import unittest
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import tempfile
|
||||
|
||||
|
@ -85,6 +86,10 @@ def _copy_lines_between_files(fin, fout, n=None, skip=0, mode='a', terminal_line
|
|||
|
||||
Returns open fout
|
||||
"""
|
||||
if sys.version_info[:2] <= (2,4):
|
||||
# on old Python st_mtime is int, so we should give at least 1 sec so
|
||||
# polling filter could detect the change
|
||||
time.sleep(1)
|
||||
if isinstance(fin, str):
|
||||
fin = open(fin, 'r')
|
||||
if isinstance(fout, str):
|
||||
|
@ -315,6 +320,9 @@ def get_monitor_failures_testcase(Filter_):
|
|||
self.filter.setActive(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.start()
|
||||
# If filter is polling it would sleep a bit to guarantee that
|
||||
# we have initial time-stamp difference to trigger "actions"
|
||||
self._sleep_4_poll()
|
||||
#print "D: started filter %s" % self.filter
|
||||
|
||||
|
||||
|
@ -326,7 +334,7 @@ def get_monitor_failures_testcase(Filter_):
|
|||
#print "D: WAITING FOR FILTER TO STOP"
|
||||
self.filter.join() # wait for the thread to terminate
|
||||
#print "D: KILLING THE FILE"
|
||||
#_killfile(self.file, self.name)
|
||||
_killfile(self.file, self.name)
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
|
@ -343,6 +351,19 @@ def get_monitor_failures_testcase(Filter_):
|
|||
time.sleep(0.1)
|
||||
return False
|
||||
|
||||
def _sleep_4_poll(self):
|
||||
# Since FilterPoll relies on time stamps and some
|
||||
# actions might be happening too fast in the tests,
|
||||
# sleep a bit to guarantee reliable time stamps
|
||||
if isinstance(self.filter, FilterPoll):
|
||||
if sys.version_info[:2] <= (2,4):
|
||||
# on old Python st_mtime is int, so we should give
|
||||
# at least 1 sec so polling filter could detect
|
||||
# the change
|
||||
time.sleep(0.5)
|
||||
else:
|
||||
time.sleep(0.1)
|
||||
|
||||
def isEmpty(self, delay=0.4):
|
||||
# shorter wait time for not modified status
|
||||
return not self.isFilled(delay)
|
||||
|
@ -354,7 +375,6 @@ def get_monitor_failures_testcase(Filter_):
|
|||
|
||||
def test_grow_file(self):
|
||||
# suck in lines from this sample log file
|
||||
#self.filter.getFailures(self.name)
|
||||
self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan)
|
||||
|
||||
# Now let's feed it with entries from the file
|
||||
|
@ -365,7 +385,7 @@ def get_monitor_failures_testcase(Filter_):
|
|||
# since it should have not been enough
|
||||
|
||||
_copy_lines_between_files(GetFailures.FILENAME_01, self.file, skip=5)
|
||||
self.isFilled(6)
|
||||
self.assertTrue(self.isFilled(6))
|
||||
# so we sleep for up to 2 sec for it not to become empty,
|
||||
# and meanwhile pass to other thread(s) and filter should
|
||||
# have gathered new failures and passed them into the
|
||||
|
@ -383,7 +403,6 @@ def get_monitor_failures_testcase(Filter_):
|
|||
self.assert_correct_last_attempt(GetFailures.FAILURES_01)
|
||||
|
||||
def test_rewrite_file(self):
|
||||
#
|
||||
# if we rewrite the file at once
|
||||
self.file.close()
|
||||
_copy_lines_between_files(GetFailures.FILENAME_01, self.name)
|
||||
|
@ -399,11 +418,10 @@ def get_monitor_failures_testcase(Filter_):
|
|||
|
||||
|
||||
def test_move_file(self):
|
||||
#
|
||||
# if we move file into a new location while it has been open already
|
||||
self.file = _copy_lines_between_files(GetFailures.FILENAME_01, self.name,
|
||||
n=14, mode='w')
|
||||
self.isFilled(6)
|
||||
self.assertTrue(self.isEmpty(2))
|
||||
self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan)
|
||||
self.assertEqual(self.filter.failManager.getFailTotal(), 2) # Fails with Poll from time to time
|
||||
|
||||
|
@ -413,6 +431,25 @@ def get_monitor_failures_testcase(Filter_):
|
|||
self.assert_correct_last_attempt(GetFailures.FAILURES_01)
|
||||
self.assertEqual(self.filter.failManager.getFailTotal(), 3)
|
||||
|
||||
# now remove the moved file
|
||||
_killfile(None, self.name + '.bak')
|
||||
_copy_lines_between_files(GetFailures.FILENAME_01, self.name, n=100)
|
||||
self.assert_correct_last_attempt(GetFailures.FAILURES_01)
|
||||
self.assertEqual(self.filter.failManager.getFailTotal(), 6)
|
||||
|
||||
|
||||
def test_new_bogus_file(self):
|
||||
# to make sure that watching whole directory does not effect
|
||||
_copy_lines_between_files(GetFailures.FILENAME_01, self.name, n=100)
|
||||
self.assert_correct_last_attempt(GetFailures.FAILURES_01)
|
||||
|
||||
# create a bogus file in the same directory and see if that doesn't affect
|
||||
open(self.name + '.bak2', 'w').write('')
|
||||
_copy_lines_between_files(GetFailures.FILENAME_01, self.name, n=100)
|
||||
self.assert_correct_last_attempt(GetFailures.FAILURES_01)
|
||||
self.assertEqual(self.filter.failManager.getFailTotal(), 6)
|
||||
_killfile(None, self.name + '.bak2')
|
||||
|
||||
|
||||
def test_delLogPath(self):
|
||||
# Smoke test for removing of the path from being watched
|
||||
|
|
Loading…
Reference in New Issue