summaryrefslogtreecommitdiff
path: root/fail2ban
diff options
context:
space:
mode:
Diffstat (limited to 'fail2ban')
-rw-r--r--fail2ban/client/beautifier.py4
-rw-r--r--fail2ban/client/configreader.py41
-rw-r--r--fail2ban/server/action.py50
-rw-r--r--fail2ban/server/datedetector.py13
-rw-r--r--fail2ban/server/datetemplate.py2
-rw-r--r--fail2ban/server/failregex.py8
-rw-r--r--fail2ban/server/filter.py18
-rw-r--r--fail2ban/server/filterpyinotify.py4
-rw-r--r--fail2ban/server/mytime.py12
-rw-r--r--fail2ban/server/server.py2
-rw-r--r--fail2ban/tests/actiontestcase.py21
-rw-r--r--fail2ban/tests/files/logs/named-refused1
-rw-r--r--fail2ban/tests/filtertestcase.py34
13 files changed, 131 insertions, 79 deletions
diff --git a/fail2ban/client/beautifier.py b/fail2ban/client/beautifier.py
index a92e9520..542611d4 100644
--- a/fail2ban/client/beautifier.py
+++ b/fail2ban/client/beautifier.py
@@ -56,10 +56,10 @@ class Beautifier:
msg = "Jail started"
elif inC[0] == "stop":
if len(inC) == 1:
- if response == None:
+ if response is None:
msg = "Shutdown successful"
else:
- if response == None:
+ if response is None:
msg = "Jail stopped"
elif inC[0] == "add":
msg = "Added jail " + response
diff --git a/fail2ban/client/configreader.py b/fail2ban/client/configreader.py
index ac6af8f7..5d2e8bf1 100644
--- a/fail2ban/client/configreader.py
+++ b/fail2ban/client/configreader.py
@@ -52,9 +52,9 @@ class ConfigReader(SafeConfigParserWithIncludes):
return self._basedir
def read(self, filename):
- if not (os.path.exists(self._basedir) and os.access(self._basedir, os.R_OK | os.X_OK)):
- raise ValueError("Base configuration directory %s either does not exist "
- "or is not accessible" % self._basedir)
+ if not os.path.exists(self._basedir):
+ raise ValueError("Base configuration directory %s does not exist "
+ % self._basedir)
basename = os.path.join(self._basedir, filename)
logSys.debug("Reading configs for %s under %s " % (basename, self._basedir))
config_files = [ basename + ".conf",
@@ -65,27 +65,20 @@ class ConfigReader(SafeConfigParserWithIncludes):
# possible further customizations under a .conf.d directory
config_dir = basename + '.d'
- if os.path.exists(config_dir):
- if os.path.isdir(config_dir) and os.access(config_dir, os.X_OK | os.R_OK):
- # files must carry .conf suffix as well
- config_files += sorted(glob.glob('%s/*.conf' % config_dir))
- else:
- logSys.warning("%s exists but not a directory or not accessible"
- % config_dir)
+ config_files += sorted(glob.glob('%s/*.conf' % config_dir))
- # check if files are accessible, warn if any is not accessible
- # and remove it from the list
- config_files_accessible = []
- for f in config_files:
- if os.access(f, os.R_OK):
- config_files_accessible.append(f)
- else:
- logSys.warning("%s exists but not accessible - skipping" % f)
-
- if len(config_files_accessible):
+ if len(config_files):
# at least one config exists and accessible
- SafeConfigParserWithIncludes.read(self, config_files_accessible)
- return True
+ logSys.debug("Reading config files: " + ', '.join(config_files))
+ config_files_read = SafeConfigParserWithIncludes.read(self, config_files)
+ missed = [ cf for cf in config_files if cf not in config_files_read ]
+ if missed:
+ logSys.error("Could not read config files: " + ', '.join(missed))
+ if config_files_read:
+ return True
+ logSys.error("Found no accessible config files for %r under %s" %
+ ( filename, self.getBaseDir() ))
+ return False
else:
logSys.error("Found no accessible config files for %r " % filename
+ (["under %s" % self.getBaseDir(),
@@ -113,7 +106,7 @@ class ConfigReader(SafeConfigParserWithIncludes):
v = self.getint(sec, option[1])
else:
v = self.get(sec, option[1])
- if not pOptions == None and option[1] in pOptions:
+ if not pOptions is None and option[1] in pOptions:
continue
values[option[1]] = v
except NoSectionError, e:
@@ -121,7 +114,7 @@ class ConfigReader(SafeConfigParserWithIncludes):
logSys.error(e)
values[option[1]] = option[2]
except NoOptionError:
- if not option[2] == None:
+ if not option[2] is None:
logSys.warning("'%s' not defined in '%s'. Using default one: %r"
% (option[1], sec, option[2]))
values[option[1]] = option[2]
diff --git a/fail2ban/server/action.py b/fail2ban/server/action.py
index b8356799..baccca08 100644
--- a/fail2ban/server/action.py
+++ b/fail2ban/server/action.py
@@ -17,18 +17,12 @@
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# Author: Cyril Jaquier
-#
-# $Revision$
-
-__author__ = "Cyril Jaquier"
-__version__ = "$Revision$"
-__date__ = "$Date$"
-__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
+__author__ = "Cyril Jaquier and Fail2Ban Contributors"
+__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2012 Yaroslav Halchenko"
__license__ = "GPL"
import logging, os
-import threading
+import threading, re
#from subprocess import call
# Gets the instance of the logger.
@@ -143,6 +137,10 @@ class Action:
# @return True if the command succeeded
def execActionStart(self):
+ if self.__cInfo:
+ if not Action.substituteRecursiveTags(self.__cInfo):
+ logSys.error("Cinfo/definitions contain self referencing definitions and cannot be resolved")
+ return False
startCmd = Action.replaceTag(self.__actionStart, self.__cInfo)
return Action.executeCmd(startCmd)
@@ -242,6 +240,38 @@ class Action:
stopCmd = Action.replaceTag(self.__actionStop, self.__cInfo)
return Action.executeCmd(stopCmd)
+ ##
+ # Sort out tag definitions within other tags
+ #
+ # so: becomes:
+ # a = 3 a = 3
+ # b = <a>_3 b = 3_3
+ # @param tags, a dictionary
+ # @returns tags altered or False if there is a recursive definition
+ #@staticmethod
+ def substituteRecursiveTags(tags):
+ t = re.compile(r'<([^ >]+)>')
+ for tag, value in tags.iteritems():
+ value = str(value)
+ m = t.search(value)
+ while m:
+ if m.group(1) == tag:
+ # recursive definitions are bad
+ return False
+ else:
+ if tags.has_key(m.group(1)):
+ value = value[0:m.start()] + tags[m.group(1)] + value[m.end():]
+ m = t.search(value, m.start())
+ else:
+ # Missing tags are ok so we just continue on searching.
+ # cInfo can contain aInfo elements like <HOST> and valid shell
+ # constructs like <STDIN>.
+ m = t.search(value, m.start() + 1)
+ tags[tag] = value
+ return tags
+ substituteRecursiveTags = staticmethod(substituteRecursiveTags)
+
+ #@staticmethod
def escapeTag(tag):
for c in '\\#&;`|*?~<>^()[]{}$\n\'"':
if c in tag:
@@ -304,7 +334,7 @@ class Action:
return False
# Replace tags
- if not aInfo == None:
+ if not aInfo is None:
realCmd = Action.replaceTag(cmd, aInfo)
else:
realCmd = cmd
diff --git a/fail2ban/server/datedetector.py b/fail2ban/server/datedetector.py
index a29c9757..6b66253b 100644
--- a/fail2ban/server/datedetector.py
+++ b/fail2ban/server/datedetector.py
@@ -17,13 +17,7 @@
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# Author: Cyril Jaquier
-#
-# $Revision$
-
-__author__ = "Cyril Jaquier"
-__version__ = "$Revision$"
-__date__ = "$Date$"
+__author__ = "Cyril Jaquier and Fail2Ban Contributors"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
@@ -203,10 +197,7 @@ class DateDetector:
def getUnixTime(self, line):
date = self.getTime(line)
- if date == None:
- return None
- else:
- return time.mktime(tuple(date))
+ return date and time.mktime(tuple(date))
##
# Sort the template lists using the hits score. This method is not called
diff --git a/fail2ban/server/datetemplate.py b/fail2ban/server/datetemplate.py
index f77f00c9..94523fba 100644
--- a/fail2ban/server/datetemplate.py
+++ b/fail2ban/server/datetemplate.py
@@ -65,7 +65,7 @@ class DateTemplate:
def matchDate(self, line):
dateMatch = self.__cRegex.search(line)
- if not dateMatch == None:
+ if not dateMatch is None:
self.__hits += 1
return dateMatch
diff --git a/fail2ban/server/failregex.py b/fail2ban/server/failregex.py
index 890cd364..3d05ad55 100644
--- a/fail2ban/server/failregex.py
+++ b/fail2ban/server/failregex.py
@@ -17,13 +17,7 @@
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# Author: Cyril Jaquier
-#
-# $Revision$
-
__author__ = "Cyril Jaquier"
-__version__ = "$Revision$"
-__date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
@@ -191,7 +185,7 @@ class FailRegex(Regex):
def getHost(self):
host = self._matchCache.group("host")
- if host == None:
+ if host is None:
# Gets a few information.
s = self._matchCache.string
r = self._matchCache.re
diff --git a/fail2ban/server/filter.py b/fail2ban/server/filter.py
index d4108dc2..1e71c55f 100644
--- a/fail2ban/server/filter.py
+++ b/fail2ban/server/filter.py
@@ -17,14 +17,8 @@
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# Author: Cyril Jaquier
-#
-# $Revision$
-
-__author__ = "Cyril Jaquier"
-__version__ = "$Revision$"
-__date__ = "$Date$"
-__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
+__author__ = "Cyril Jaquier and Fail2Ban Contributors"
+__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2013 Yaroslav Halchenko"
__license__ = "GPL"
from failmanager import FailManagerEmpty
@@ -384,7 +378,7 @@ class Filter(JailThread):
continue
# The failregex matched.
date = self.dateDetector.getUnixTime(timeLine)
- if date == None:
+ if date is None:
logSys.debug("Found a match for %r but no valid date/time "
"found for %r. Please file a detailed issue on"
" https://github.com/fail2ban/fail2ban/issues "
@@ -521,7 +515,7 @@ class FileFilter(Filter):
def getFailures(self, filename):
container = self.getFileContainer(filename)
- if container == None:
+ if container is None:
logSys.error("Unable to get failures in " + filename)
return False
# Try to open log file.
@@ -626,7 +620,7 @@ class FileContainer:
self.__handler.seek(self.__pos)
def readline(self):
- if self.__handler == None:
+ if self.__handler is None:
return ""
line = self.__handler.readline()
try:
@@ -639,7 +633,7 @@ class FileContainer:
return line
def close(self):
- if not self.__handler == None:
+ if not self.__handler is None:
# Saves the last position.
self.__pos = self.__handler.tell()
# Closes the file.
diff --git a/fail2ban/server/filterpyinotify.py b/fail2ban/server/filterpyinotify.py
index 786c6dfa..03623ddb 100644
--- a/fail2ban/server/filterpyinotify.py
+++ b/fail2ban/server/filterpyinotify.py
@@ -66,7 +66,7 @@ class FilterPyinotify(FileFilter):
def callback(self, event, origin=''):
logSys.debug("%sCallback for Event: %s", origin, event)
path = event.pathname
- if event.mask & pyinotify.IN_CREATE:
+ if event.mask & ( pyinotify.IN_CREATE | pyinotify.IN_MOVED_TO ):
# skip directories altogether
if event.mask & pyinotify.IN_ISDIR:
logSys.debug("Ignoring creation of directory %s", path)
@@ -130,7 +130,7 @@ class FilterPyinotify(FileFilter):
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))
+ self.__monitor.add_watch(path_dir, pyinotify.IN_CREATE | pyinotify.IN_MOVED_TO))
logSys.debug("Added monitor for the parent directory %s", path_dir)
self._addFileWatcher(path)
diff --git a/fail2ban/server/mytime.py b/fail2ban/server/mytime.py
index 286f3d2c..8ae85184 100644
--- a/fail2ban/server/mytime.py
+++ b/fail2ban/server/mytime.py
@@ -17,13 +17,7 @@
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-# Author: Cyril Jaquier
-#
-# $Revision$
-
__author__ = "Cyril Jaquier"
-__version__ = "$Revision$"
-__date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
@@ -61,7 +55,7 @@ class MyTime:
#@staticmethod
def time():
- if MyTime.myTime == None:
+ if MyTime.myTime is None:
return time.time()
else:
return MyTime.myTime
@@ -74,14 +68,14 @@ class MyTime:
#@staticmethod
def gmtime():
- if MyTime.myTime == None:
+ if MyTime.myTime is None:
return time.gmtime()
else:
return time.gmtime(MyTime.myTime)
gmtime = staticmethod(gmtime)
def localtime(x=None):
- if MyTime.myTime == None or x is not None:
+ if MyTime.myTime is None or x is not None:
return time.localtime(x)
else:
return time.localtime(MyTime.myTime)
diff --git a/fail2ban/server/server.py b/fail2ban/server/server.py
index 092867bf..f194b3a1 100644
--- a/fail2ban/server/server.py
+++ b/fail2ban/server/server.py
@@ -402,7 +402,7 @@ class Server:
hdlr.setFormatter(formatter)
logger.addHandler(hdlr)
# Does not display this message at startup.
- if not self.__logTarget == None:
+ if not self.__logTarget is None:
logSys.info("Changed logging target to %s for Fail2ban v%s" %
(target, version.version))
# Sets the logging target.
diff --git a/fail2ban/tests/actiontestcase.py b/fail2ban/tests/actiontestcase.py
index 54c37ef0..8a6ea8ac 100644
--- a/fail2ban/tests/actiontestcase.py
+++ b/fail2ban/tests/actiontestcase.py
@@ -62,6 +62,27 @@ class ExecuteAction(unittest.TestCase):
def _is_logged(self, s):
return s in self._log.getvalue()
+ def testSubstituteRecursiveTags(self):
+ aInfo = {
+ 'HOST': "192.0.2.0",
+ 'ABC': "123 <HOST>",
+ 'xyz': "890 <ABC>",
+ }
+ # Recursion is bad
+ self.assertFalse(Action.substituteRecursiveTags({'A': '<A>'}))
+ self.assertFalse(Action.substituteRecursiveTags({'A': '<B>', 'B': '<A>'}))
+ self.assertFalse(Action.substituteRecursiveTags({'A': '<B>', 'B': '<C>', 'C': '<A>'}))
+ # missing tags are ok
+ self.assertEquals(Action.substituteRecursiveTags({'A': '<C>'}), {'A': '<C>'})
+ self.assertEquals(Action.substituteRecursiveTags({'A': '<C> <D> <X>','X':'fun'}), {'A': '<C> <D> fun', 'X':'fun'})
+ self.assertEquals(Action.substituteRecursiveTags({'A': '<C> <B>', 'B': 'cool'}), {'A': '<C> cool', 'B': 'cool'})
+ # rest is just cool
+ self.assertEquals(Action.substituteRecursiveTags(aInfo),
+ { 'HOST': "192.0.2.0",
+ 'ABC': '123 192.0.2.0',
+ 'xyz': '890 123 192.0.2.0',
+ })
+
def testReplaceTag(self):
aInfo = {
'HOST': "192.0.2.0",
diff --git a/fail2ban/tests/files/logs/named-refused b/fail2ban/tests/files/logs/named-refused
index 6608ae2f..130e7417 100644
--- a/fail2ban/tests/files/logs/named-refused
+++ b/fail2ban/tests/files/logs/named-refused
@@ -3,3 +3,4 @@ Jul 24 14:16:56 raid5 named[3935]: client 62.123.164.113#32768: query 'ricreig.c
Jul 24 14:17:13 raid5 named[3935]: client 148.160.29.6#33081: query (cache) 'geo-mueller.de/NS/IN' denied
Jul 24 14:20:25 raid5 named[3935]: client 148.160.29.6#33081: query (cache) 'shivaree.de/NS/IN' denied
Jul 24 14:23:36 raid5 named[3935]: client 148.160.29.6#33081: query (cache) 'mietberatung.de/NS/IN' denied
+Jul 24 14:23:36 raid5 named[3935]: client 62.109.4.89#9334: view external: query (cache) './NS/IN' denied
diff --git a/fail2ban/tests/filtertestcase.py b/fail2ban/tests/filtertestcase.py
index e540e41e..b110431f 100644
--- a/fail2ban/tests/filtertestcase.py
+++ b/fail2ban/tests/filtertestcase.py
@@ -495,6 +495,40 @@ def get_monitor_failures_testcase(Filter_):
self.assertEqual(self.filter.failManager.getFailTotal(), 6)
+ def _test_move_into_file(self, interim_kill=False):
+ # if we move a new file into the location of an old (monitored) file
+ self.file1 = _copy_lines_between_files(GetFailures.FILENAME_01, self.name,
+ n=100)
+ # make sure that it is monitored first
+ self.assert_correct_last_attempt(GetFailures.FAILURES_01)
+ self.assertEqual(self.filter.failManager.getFailTotal(), 3)
+
+ if interim_kill:
+ _killfile(None, self.name)
+ time.sleep(0.2) # let them know
+
+ # now create a new one to override old one
+ self.file = _copy_lines_between_files(GetFailures.FILENAME_01,
+ self.name + '.new', n=100)
+ os.rename(self.name + '.new', self.name)
+ self.assert_correct_last_attempt(GetFailures.FAILURES_01)
+ self.assertEqual(self.filter.failManager.getFailTotal(), 6)
+
+ # and to make sure that it now monitored for changes
+ _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(), 9)
+
+
+ def test_move_into_file(self):
+ self._test_move_into_file(interim_kill=False)
+
+ def test_move_into_file_after_removed(self):
+ # exactly as above test + remove file explicitly
+ # to test against possible drop-out of the file from monitoring
+ self._test_move_into_file(interim_kill=True)
+
+
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).close()