diff --git a/ChangeLog b/ChangeLog index aa432dca..782ba7c9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -70,6 +70,7 @@ ver. 0.9.2 (2014/XX/XXX) - wanna-be-released timezone not UTC time/zone. Closes gh-911 * Conditionally log Ignore IP with reason (dns, ip, command). Closes gh-916 * Absorbed DNSUtils.cidr into addr2bin in filter.py, added unittests + * Added syslogsocket configuration to fail2ban.conf ver. 0.9.1 (2014/10/29) - better, faster, stronger diff --git a/config/fail2ban.conf b/config/fail2ban.conf index 1a22a9e8..89e2538e 100644 --- a/config/fail2ban.conf +++ b/config/fail2ban.conf @@ -34,6 +34,12 @@ loglevel = INFO # logtarget = /var/log/fail2ban.log +# Option: syslogsocket +# Notes: Set the syslog socket file. Only used when logtarget is SYSLOG +# auto uses platform.system() to determine predefined paths +# Values: [ auto | FILE ] Default: auto +syslogsocket = auto + # Option: socket # Notes.: Set the socket file. This is used to communicate with the daemon. Do # not remove this file when Fail2ban runs. It will not be possible to diff --git a/fail2ban/client/beautifier.py b/fail2ban/client/beautifier.py index d94216c8..cbe4d327 100644 --- a/fail2ban/client/beautifier.py +++ b/fail2ban/client/beautifier.py @@ -85,6 +85,9 @@ class Beautifier: val = " ".join(res1[1]) if isinstance(res1[1], list) else res1[1] msg.append("%s %s:\t%s" % (prefix1, res1[0], val)) msg = "\n".join(msg) + elif inC[1] == "syslogsocket": + msg = "Current syslog socket is:\n" + msg = msg + "`- " + response elif inC[1] == "logtarget": msg = "Current logging target is:\n" msg = msg + "`- " + response diff --git a/fail2ban/client/fail2banreader.py b/fail2ban/client/fail2banreader.py index 361a5e54..c2f71d06 100644 --- a/fail2ban/client/fail2banreader.py +++ b/fail2ban/client/fail2banreader.py @@ -46,6 +46,7 @@ class Fail2banReader(ConfigReader): def getOptions(self): opts = [["string", "loglevel", "INFO" ], ["string", "logtarget", "STDERR"], + ["string", "syslogsocket", "auto"], ["string", "dbfile", "/var/lib/fail2ban/fail2ban.sqlite3"], ["int", "dbpurgeage", 86400]] self.__opts = ConfigReader.getOptions(self, "Definition", opts) @@ -57,6 +58,8 @@ class Fail2banReader(ConfigReader): stream.append(["set", "loglevel", self.__opts[opt]]) elif opt == "logtarget": stream.append(["set", "logtarget", self.__opts[opt]]) + elif opt == "syslogsocket": + stream.append(["set", "syslogsocket", self.__opts[opt]]) elif opt == "dbfile": stream.append(["set", "dbfile", self.__opts[opt]]) elif opt == "dbpurgeage": diff --git a/fail2ban/protocol.py b/fail2ban/protocol.py index 4d421dd0..9218b736 100644 --- a/fail2ban/protocol.py +++ b/fail2ban/protocol.py @@ -44,6 +44,8 @@ protocol = [ ["get loglevel", "gets the logging level"], ["set logtarget ", "sets logging target to . Can be STDOUT, STDERR, SYSLOG or a file"], ["get logtarget", "gets logging target"], +["set syslogsocket auto|", "sets the syslog socket path to auto or . Only used if logtarget is SYSLOG"], +["get syslogsocket", "gets syslog socket path"], ["flushlogs", "flushes the logtarget if a file and reopens it. For log rotation."], ['', "DATABASE", ""], ["set dbfile ", "set the location of fail2ban persistent datastore. Set to \"None\" to disable"], diff --git a/fail2ban/server/server.py b/fail2ban/server/server.py index d57f2cce..bb9d28c7 100644 --- a/fail2ban/server/server.py +++ b/fail2ban/server/server.py @@ -25,7 +25,7 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" from threading import Lock, RLock -import logging, logging.handlers, sys, os, signal +import logging, logging.handlers, sys, os, signal, stat from .jails import Jails from .filter import FileFilter, JournalFilter @@ -55,10 +55,18 @@ class Server: self.__asyncServer = AsyncServer(self.__transm) self.__logLevel = None self.__logTarget = None + self.__syslogSocket = None + self.__autoSyslogSocketPaths = { + 'Darwin': '/var/run/syslog', + 'FreeBSD': '/var/run/log', + 'Linux': '/dev/log', + } # Set logging level self.setLogLevel("INFO") self.setLogTarget("STDOUT") - + self.setSyslogSocket("auto") + + def __sigTERMhandler(self, signum, frame): logSys.debug("Caught signal %d. Exiting" % signum) self.quit() @@ -360,7 +368,7 @@ class Server: return self.__logLevel finally: self.__loggingLock.release() - + ## # Sets the logging target. # @@ -376,7 +384,21 @@ class Server: # Syslog daemons already add date to the message. formatter = logging.Formatter("%(name)s[%(process)d]: %(levelname)s %(message)s") facility = logging.handlers.SysLogHandler.LOG_DAEMON - hdlr = logging.handlers.SysLogHandler("/dev/log", facility=facility) + if self.__syslogSocket == "auto": + import platform + self.__syslogSocket = self.__autoSyslogSocketPaths.get( + platform.system()) + if self.__syslogSocket is not None\ + and os.path.exists(self.__syslogSocket)\ + and stat.S_ISSOCK(os.stat( + self.__syslogSocket).st_mode): + hdlr = logging.handlers.SysLogHandler( + self.__syslogSocket, facility=facility) + else: + logSys.error( + "Syslog socket file: %s does not exists" + " or is not a socket" % self.__syslogSocket) + return False elif target == "STDOUT": hdlr = logging.StreamHandler(sys.stdout) elif target == "STDERR": @@ -412,21 +434,44 @@ class Server: logger.addHandler(hdlr) # Does not display this message at startup. if not self.__logTarget is None: - logSys.info("Changed logging target to %s for Fail2ban v%s" % - (target, version.version)) + logSys.info( + "Changed logging target to %s for Fail2ban v%s" + % ((target + if target != "SYSLOG" + else "%s (%s)" + % (target, self.__syslogSocket)), + version.version)) # Sets the logging target. self.__logTarget = target return True finally: self.__loggingLock.release() - + + ## + # Sets the syslog socket. + # + # syslogsocket is the full path to the syslog socket + # @param syslogsocket the syslog socket path + def setSyslogSocket(self, syslogsocket): + self.__syslogSocket = syslogsocket + # Conditionally reload, logtarget depends on socket path when SYSLOG + return self.__logTarget != "SYSLOG"\ + or self.setLogTarget(self.__logTarget) + def getLogTarget(self): try: self.__loggingLock.acquire() return self.__logTarget finally: self.__loggingLock.release() - + + def getSyslogSocket(self): + try: + self.__loggingLock.acquire() + return self.__syslogSocket + finally: + self.__loggingLock.release() + def flushLogs(self): if self.__logTarget not in ['STDERR', 'STDOUT', 'SYSLOG']: for handler in getLogger("fail2ban").handlers: diff --git a/fail2ban/server/transmitter.py b/fail2ban/server/transmitter.py index 58663590..5bf62128 100644 --- a/fail2ban/server/transmitter.py +++ b/fail2ban/server/transmitter.py @@ -121,6 +121,12 @@ class Transmitter: return self.__server.getLogTarget() else: raise Exception("Failed to change log target") + elif name == "syslogsocket": + value = command[1] + if self.__server.setSyslogSocket(value): + return self.__server.getSyslogSocket() + else: + raise Exception("Failed to change syslog socket") #Database elif name == "dbfile": self.__server.setDatabase(command[1]) @@ -264,6 +270,8 @@ class Transmitter: return self.__server.getLogLevel() elif name == "logtarget": return self.__server.getLogTarget() + elif name == "syslogsocket": + return self.__server.getSyslogSocket() #Database elif name == "dbfile": db = self.__server.getDatabase() diff --git a/fail2ban/tests/clientreadertestcase.py b/fail2ban/tests/clientreadertestcase.py index a94fad4e..941116fd 100644 --- a/fail2ban/tests/clientreadertestcase.py +++ b/fail2ban/tests/clientreadertestcase.py @@ -623,7 +623,8 @@ class JailsReaderTest(LogCaptureTestCase): '/var/lib/fail2ban/fail2ban.sqlite3'], ['set', 'dbpurgeage', 86400], ['set', 'loglevel', "INFO"], - ['set', 'logtarget', '/var/log/fail2ban.log']]) + ['set', 'logtarget', '/var/log/fail2ban.log'], + ['set', 'syslogsocket', 'auto']]) # and if we force change configurator's fail2ban's baseDir # there should be an error message (test visually ;) -- diff --git a/fail2ban/tests/servertestcase.py b/fail2ban/tests/servertestcase.py index 556f4b97..2475fa99 100644 --- a/fail2ban/tests/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -739,6 +739,7 @@ class TransmitterLogging(TransmitterBase): self.server = Server() self.server.setLogTarget("/dev/null") self.server.setLogLevel("CRITICAL") + self.server.setSyslogSocket("auto") super(TransmitterLogging, self).setUp() def testLogTarget(self): @@ -766,6 +767,18 @@ class TransmitterLogging(TransmitterBase): raise unittest.SkipTest("'/dev/log' not present") elif not os.path.exists("/dev/log"): return + self.assertTrue(self.server.getSyslogSocket(), "auto") + self.setGetTest("logtarget", "SYSLOG") + self.assertTrue(self.server.getSyslogSocket(), "/dev/log") + + def testSyslogSocket(self): + self.setGetTest("syslogsocket", "/dev/log/NEW/PATH") + + def testSyslogSocketNOK(self): + self.setGetTest("syslogsocket", "/this/path/should/not/exist") + self.setGetTestNOK("logtarget", "SYSLOG") + # set back for other tests + self.setGetTest("syslogsocket", "/dev/log") self.setGetTest("logtarget", "SYSLOG") def testLogLevel(self):