diff options
Diffstat (limited to 'fail2ban')
-rw-r--r-- | fail2ban/client/beautifier.py | 4 | ||||
-rw-r--r-- | fail2ban/client/configreader.py | 41 | ||||
-rw-r--r-- | fail2ban/server/action.py | 50 | ||||
-rw-r--r-- | fail2ban/server/datedetector.py | 13 | ||||
-rw-r--r-- | fail2ban/server/datetemplate.py | 2 | ||||
-rw-r--r-- | fail2ban/server/failregex.py | 8 | ||||
-rw-r--r-- | fail2ban/server/filter.py | 18 | ||||
-rw-r--r-- | fail2ban/server/filterpyinotify.py | 4 | ||||
-rw-r--r-- | fail2ban/server/mytime.py | 12 | ||||
-rw-r--r-- | fail2ban/server/server.py | 2 | ||||
-rw-r--r-- | fail2ban/tests/actiontestcase.py | 21 | ||||
-rw-r--r-- | fail2ban/tests/files/logs/named-refused | 1 | ||||
-rw-r--r-- | fail2ban/tests/filtertestcase.py | 34 |
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() |