From 445fd7367fe0ca884ea0af319874101bd799035b Mon Sep 17 00:00:00 2001 From: Lee Clemens Date: Thu, 5 Feb 2015 23:44:57 -0500 Subject: [PATCH 1/3] Configure Syslog Socket Path --- config/fail2ban.conf | 5 +++ fail2ban/client/beautifier.py | 3 ++ fail2ban/client/fail2banreader.py | 3 ++ fail2ban/protocol.py | 2 ++ fail2ban/server/server.py | 48 ++++++++++++++++++++++---- fail2ban/server/transmitter.py | 8 +++++ fail2ban/tests/clientreadertestcase.py | 3 +- fail2ban/tests/servertestcase.py | 11 ++++++ 8 files changed, 75 insertions(+), 8 deletions(-) diff --git a/config/fail2ban.conf b/config/fail2ban.conf index 1a22a9e8..abed5159 100644 --- a/config/fail2ban.conf +++ b/config/fail2ban.conf @@ -34,6 +34,11 @@ loglevel = INFO # logtarget = /var/log/fail2ban.log +# Option: syslogsocket +# Notes: Set the syslog socket file. Only used when logtarget is SYSLOG +# Values: [ FILE ] Default: /dev/log +syslogsocket = /dev/log + # 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..09b39d8d 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", "/dev/log"], ["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..3b9cf412 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 ", "sets the syslog socket path to . 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..f6b30ef0 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,12 @@ class Server: self.__asyncServer = AsyncServer(self.__transm) self.__logLevel = None self.__logTarget = None + self.__syslogSocket = None # Set logging level self.setLogLevel("INFO") self.setLogTarget("STDOUT") - + self.setSyslogSocket("/dev/log") + def __sigTERMhandler(self, signum, frame): logSys.debug("Caught signal %d. Exiting" % signum) self.quit() @@ -376,7 +378,16 @@ 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 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 +423,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..81c803e7 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', '/dev/log']]) # 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..970068d8 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("/dev/log") super(TransmitterLogging, self).setUp() def testLogTarget(self): @@ -768,6 +769,16 @@ class TransmitterLogging(TransmitterBase): return self.setGetTest("logtarget", "SYSLOG") + 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): self.setGetTest("loglevel", "HEAVYDEBUG") self.setGetTest("loglevel", "DEBUG") From d676a9fd4fbfbe6c150011bfdf23dc47af6cb01e Mon Sep 17 00:00:00 2001 From: Lee Clemens Date: Thu, 5 Feb 2015 23:48:18 -0500 Subject: [PATCH 2/3] update ChangeLog with syslogsocket config enhancement --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index 96cef17d..c2e19ee2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -69,6 +69,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 From 6268eb32beb9eada9c4ab7fd81d279936154835c Mon Sep 17 00:00:00 2001 From: Lee Clemens Date: Fri, 6 Feb 2015 19:14:09 -0500 Subject: [PATCH 3/3] Use syslogsocket value "auto" to determine syslog socket's path --- config/fail2ban.conf | 5 +++-- fail2ban/client/fail2banreader.py | 2 +- fail2ban/protocol.py | 2 +- fail2ban/server/server.py | 17 ++++++++++++++--- fail2ban/tests/clientreadertestcase.py | 2 +- fail2ban/tests/servertestcase.py | 4 +++- 6 files changed, 23 insertions(+), 9 deletions(-) diff --git a/config/fail2ban.conf b/config/fail2ban.conf index abed5159..89e2538e 100644 --- a/config/fail2ban.conf +++ b/config/fail2ban.conf @@ -36,8 +36,9 @@ logtarget = /var/log/fail2ban.log # Option: syslogsocket # Notes: Set the syslog socket file. Only used when logtarget is SYSLOG -# Values: [ FILE ] Default: /dev/log -syslogsocket = /dev/log +# 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 diff --git a/fail2ban/client/fail2banreader.py b/fail2ban/client/fail2banreader.py index 09b39d8d..c2f71d06 100644 --- a/fail2ban/client/fail2banreader.py +++ b/fail2ban/client/fail2banreader.py @@ -46,7 +46,7 @@ class Fail2banReader(ConfigReader): def getOptions(self): opts = [["string", "loglevel", "INFO" ], ["string", "logtarget", "STDERR"], - ["string", "syslogsocket", "/dev/log"], + ["string", "syslogsocket", "auto"], ["string", "dbfile", "/var/lib/fail2ban/fail2ban.sqlite3"], ["int", "dbpurgeage", 86400]] self.__opts = ConfigReader.getOptions(self, "Definition", opts) diff --git a/fail2ban/protocol.py b/fail2ban/protocol.py index 3b9cf412..9218b736 100644 --- a/fail2ban/protocol.py +++ b/fail2ban/protocol.py @@ -44,7 +44,7 @@ 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 ", "sets the syslog socket path to . Only used if logtarget is SYSLOG"], +["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", ""], diff --git a/fail2ban/server/server.py b/fail2ban/server/server.py index f6b30ef0..bb9d28c7 100644 --- a/fail2ban/server/server.py +++ b/fail2ban/server/server.py @@ -56,10 +56,16 @@ class Server: 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("/dev/log") + self.setSyslogSocket("auto") + def __sigTERMhandler(self, signum, frame): logSys.debug("Caught signal %d. Exiting" % signum) @@ -362,7 +368,7 @@ class Server: return self.__logLevel finally: self.__loggingLock.release() - + ## # Sets the logging target. # @@ -378,7 +384,12 @@ 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 - if os.path.exists(self.__syslogSocket)\ + 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( diff --git a/fail2ban/tests/clientreadertestcase.py b/fail2ban/tests/clientreadertestcase.py index 81c803e7..941116fd 100644 --- a/fail2ban/tests/clientreadertestcase.py +++ b/fail2ban/tests/clientreadertestcase.py @@ -624,7 +624,7 @@ class JailsReaderTest(LogCaptureTestCase): ['set', 'dbpurgeage', 86400], ['set', 'loglevel', "INFO"], ['set', 'logtarget', '/var/log/fail2ban.log'], - ['set', 'syslogsocket', '/dev/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 970068d8..2475fa99 100644 --- a/fail2ban/tests/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -739,7 +739,7 @@ class TransmitterLogging(TransmitterBase): self.server = Server() self.server.setLogTarget("/dev/null") self.server.setLogLevel("CRITICAL") - self.server.setSyslogSocket("/dev/log") + self.server.setSyslogSocket("auto") super(TransmitterLogging, self).setUp() def testLogTarget(self): @@ -767,7 +767,9 @@ 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")