diff --git a/.travis.yml b/.travis.yml index 362556ea..3edadda1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,5 +18,5 @@ before_script: script: - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then export PYTHONPATH="$PYTHONPATH:/usr/share/pyshared:/usr/lib/pyshared/python2.7"; fi - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then coverage run --rcfile=.travis_coveragerc bin/fail2ban-testcases; else python bin/fail2ban-testcases; fi -after_script: +after_success: - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then coveralls; fi diff --git a/ChangeLog b/ChangeLog index e5979331..9253dfc5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -17,6 +17,18 @@ Will carry all fixes in 0.8.x series and new features and enhancements - New features: Steven Hiscocks * Multiline failregex. Close gh-54 + +ver. 0.8.9 (2013/04/XXX) - wanna-be-stable +---------- + +This release incorporates 144 (XXX) non-merge commits from 14 +contributors (sorted by number of commits): Yaroslav Halchenko, Daniel +Black, Steven Hiscocks, ArndRa, hamilton5, pigsyn, Erwan Ben Souiden, +Michael Gebetsroither, Orion Poplawski, Artur Penttinen, sebres, +Nicolas Collignon, Pascal Borreli, blotus: + +- Fixes: +- New features: - Enhancements: ver. 0.8.8 (2012/12/06) - stable diff --git a/DEVELOP b/DEVELOP index 0fe80596..34c07adc 100644 --- a/DEVELOP +++ b/DEVELOP @@ -253,6 +253,10 @@ Releasing # Add/finalize the corresponding entry in the ChangeLog + To generate a list of committers use e.g. + + git shortlog -sn 0.8.8.. | sed -e 's,^[ 0-9\t]*,,g' | tr '\n' '\|' | sed -e 's:|:, :g' + # Update man pages (cd man ; ./generate-man ) diff --git a/config/fail2ban.conf b/config/fail2ban.conf index e759513b..1888eddb 100644 --- a/config/fail2ban.conf +++ b/config/fail2ban.conf @@ -43,7 +43,7 @@ socket = /var/run/fail2ban/fail2ban.sock # Option: pidfile # Notes.: Set the PID file. This is used to store the process ID of the # fail2ban server. -# Values: FILE Default: /var/run/fail2ban/fail2ban.sock +# Values: FILE Default: /var/run/fail2ban/fail2ban.pid # pidfile = /var/run/fail2ban/fail2ban.pid diff --git a/fail2ban/server/action.py b/fail2ban/server/action.py index 5fc31b28..b8356799 100644 --- a/fail2ban/server/action.py +++ b/fail2ban/server/action.py @@ -297,10 +297,8 @@ class Action: if not Action.executeCmd(checkCmd): logSys.error("Invariant check failed. Trying to restore a sane" + " environment") - stopCmd = Action.replaceTag(self.__actionStop, self.__cInfo) - Action.executeCmd(stopCmd) - startCmd = Action.replaceTag(self.__actionStart, self.__cInfo) - Action.executeCmd(startCmd) + self.execActionStop() + self.execActionStart() if not Action.executeCmd(checkCmd): logSys.fatal("Unable to restore environment") return False diff --git a/fail2ban/server/asyncserver.py b/fail2ban/server/asyncserver.py index 9d506a66..8c905010 100644 --- a/fail2ban/server/asyncserver.py +++ b/fail2ban/server/asyncserver.py @@ -28,7 +28,7 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" from pickle import dumps, loads, HIGHEST_PROTOCOL -import asyncore, asynchat, socket, os, logging, sys, traceback +import asyncore, asynchat, socket, os, logging, sys, traceback, fcntl from fail2ban import helpers @@ -118,6 +118,7 @@ class AsyncServer(asyncore.dispatcher): except TypeError: logSys.warning("Type error") return + AsyncServer.__markCloseOnExec(conn) # Creates an instance of the handler class to handle the # request/response on the incoming connection. RequestHandler(conn, self.__transmitter) @@ -145,6 +146,7 @@ class AsyncServer(asyncore.dispatcher): self.bind(sock) except Exception: raise AsyncServerException("Unable to bind socket %s" % self.__sock) + AsyncServer.__markCloseOnExec(self.socket) self.listen(1) # Sets the init flag. self.__init = True @@ -170,6 +172,18 @@ class AsyncServer(asyncore.dispatcher): os.remove(self.__sock) logSys.debug("Socket shutdown") + ## + # Marks socket as close-on-exec to avoid leaking file descriptors when + # running actions involving command execution. + + # @param sock: socket file. + + #@staticmethod + def __markCloseOnExec(sock): + fd = sock.fileno() + flags = fcntl.fcntl(fd, fcntl.F_GETFD) + fcntl.fcntl(fd, fcntl.F_SETFD, flags|fcntl.FD_CLOEXEC) + __markCloseOnExec = staticmethod(__markCloseOnExec) ## # AsyncServerException is used to wrap communication exceptions. diff --git a/fail2ban/server/filter.py b/fail2ban/server/filter.py index 161f1f56..a7062f16 100644 --- a/fail2ban/server/filter.py +++ b/fail2ban/server/filter.py @@ -610,7 +610,8 @@ class FileContainer: self.__handler = open(self.__filename, 'rb') # Set the file descriptor to be FD_CLOEXEC fd = self.__handler.fileno() - fcntl.fcntl(fd, fcntl.F_SETFD, fd | fcntl.FD_CLOEXEC) + flags = fcntl.fcntl(fd, fcntl.F_GETFD) + fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC) firstLine = self.__handler.readline() # Computes the MD5 of the first line. myHash = md5sum(firstLine).digest() diff --git a/fail2ban/server/filtergamin.py b/fail2ban/server/filtergamin.py index e324b677..b48bdd3c 100644 --- a/fail2ban/server/filtergamin.py +++ b/fail2ban/server/filtergamin.py @@ -27,7 +27,7 @@ from failmanager import FailManagerEmpty from filter import FileFilter from mytime import MyTime -import time, logging, gamin +import time, logging, gamin, fcntl # Gets the instance of the logger. logSys = logging.getLogger(__name__) @@ -52,6 +52,9 @@ class FilterGamin(FileFilter): self.__modified = False # Gamin monitor self.monitor = gamin.WatchMonitor() + fd = self.monitor.get_fd() + flags = fcntl.fcntl(fd, fcntl.F_GETFD) + fcntl.fcntl(fd, fcntl.F_SETFD, flags|fcntl.FD_CLOEXEC) logSys.debug("Created FilterGamin") diff --git a/fail2ban/server/server.py b/fail2ban/server/server.py index 7eec52d2..632d0546 100644 --- a/fail2ban/server/server.py +++ b/fail2ban/server/server.py @@ -66,7 +66,7 @@ class Server: # First set the mask to only allow access to owner os.umask(0077) - if self.__daemon: + if self.__daemon: # pragma: no cover logSys.info("Starting in daemon mode") ret = self.__createDaemon() if ret: @@ -389,7 +389,7 @@ class Server: try: handler.flush() handler.close() - except (ValueError, KeyError): + except (ValueError, KeyError): # pragma: no cover if (2,6) <= sys.version_info < (3,) or \ (3,2) <= sys.version_info: raise @@ -415,7 +415,7 @@ class Server: finally: self.__loggingLock.release() - def __createDaemon(self): + def __createDaemon(self): # pragma: no cover """ Detach a process from the controlling terminal and run it in the background as a daemon. diff --git a/fail2ban/server/transmitter.py b/fail2ban/server/transmitter.py index 7d4d2509..2d27ff6e 100644 --- a/fail2ban/server/transmitter.py +++ b/fail2ban/server/transmitter.py @@ -123,7 +123,7 @@ class Transmitter: elif command[2] == "off": self.__server.setIdleJail(name, False) else: - raise Exception("Invalid idle option, must be 'yes' or 'no'") + raise Exception("Invalid idle option, must be 'on' or 'off'") return self.__server.getIdleJail(name) # Filter elif command[1] == "addignoreip": diff --git a/fail2ban/tests/actiontestcase.py b/fail2ban/tests/actiontestcase.py index e0ea3a9b..54c37ef0 100644 --- a/fail2ban/tests/actiontestcase.py +++ b/fail2ban/tests/actiontestcase.py @@ -62,6 +62,23 @@ class ExecuteAction(unittest.TestCase): def _is_logged(self, s): return s in self._log.getvalue() + def testReplaceTag(self): + aInfo = { + 'HOST': "192.0.2.0", + 'ABC': "123", + 'xyz': "890", + } + self.assertEqual( + self.__action.replaceTag("Text text", aInfo), + "Text 192.0.2.0 text") + self.assertEqual( + self.__action.replaceTag("Text text ABC", aInfo), + "Text 890 text 123 ABC") + self.assertEqual( + self.__action.replaceTag("", + {'matches': "some >char< should \< be[ escap}ed&"}), + r"some \>char\< should \\\< be\[ escap\}ed\&") + def testExecuteActionBan(self): self.__action.setActionStart("touch /tmp/fail2ban.test") self.__action.setActionStop("rm -f /tmp/fail2ban.test") diff --git a/fail2ban/tests/servertestcase.py b/fail2ban/tests/servertestcase.py index b4c69072..385b83d4 100644 --- a/fail2ban/tests/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -520,7 +520,9 @@ class TransmitterLogging(TransmitterBase): def testLogLevel(self): self.setGetTest("loglevel", "4", 4) + self.setGetTest("loglevel", "3", 3) self.setGetTest("loglevel", "2", 2) + self.setGetTest("loglevel", "1", 1) self.setGetTest("loglevel", "-1", -1) self.setGetTest("loglevel", "0", 0) self.setGetTestNOK("loglevel", "Bird")