From bf5f46c3d597c3914ba8fa138e172aa5db10ede2 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Wed, 30 Jan 2013 19:57:03 +0000 Subject: [PATCH 01/65] Warn if config file present but unreadable --- client/configreader.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/client/configreader.py b/client/configreader.py index 063484e8..e435b254 100644 --- a/client/configreader.py +++ b/client/configreader.py @@ -59,6 +59,16 @@ class ConfigReader(SafeConfigParserWithIncludes): bConf = basename + ".conf" bLocal = basename + ".local" if os.path.exists(bConf) or os.path.exists(bLocal): + if not os.access(bConf, os.R_OK) and not os.access(bLocal, os.R_OK): + logSys.warning( + "Unable to read either \"%s\" or \"%s\"" % (bConf, bLocal)) + return False + elif os.path.exists(bConf) and not os.access(bConf, os.R_OK): + logSys.warning( + "\"%s\" read, but unable to read \"%s\"" % (bLocal, bConf)) + elif os.path.exists(bLocal) and not os.access(bLocal, os.R_OK): + logSys.warning( + "\"%s\" read, but unable to read \"%s\"" % (bConf, bLocal)) SafeConfigParserWithIncludes.read(self, [bConf, bLocal]) return True else: From 9c2e0cbbc86569108fd729c9710e42b96b1c03fa Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Thu, 31 Jan 2013 18:36:23 +0000 Subject: [PATCH 02/65] Fix up for warning/error for inaccessible config files --- client/configreader.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/client/configreader.py b/client/configreader.py index e435b254..4e783ebb 100644 --- a/client/configreader.py +++ b/client/configreader.py @@ -60,8 +60,13 @@ class ConfigReader(SafeConfigParserWithIncludes): bLocal = basename + ".local" if os.path.exists(bConf) or os.path.exists(bLocal): if not os.access(bConf, os.R_OK) and not os.access(bLocal, os.R_OK): - logSys.warning( - "Unable to read either \"%s\" or \"%s\"" % (bConf, bLocal)) + if os.path.exists(bConf) and not os.path.exists(bLocal): + logSys.error("Unable to read \"%s\" " % bConf) + elif os.path.exists(bLocal) and not os.path.exists(bConf): + logSys.error("Unable to read \"%s\" " % bLocal) + else: + logSys.error( + "Unable to read \"%s\" and \"%s\"" % (bConf, bLocal)) return False elif os.path.exists(bConf) and not os.access(bConf, os.R_OK): logSys.warning( From 52f952e6454d12fbe31dbd53bf2220c8b7658610 Mon Sep 17 00:00:00 2001 From: ArndRa Date: Mon, 11 Feb 2013 17:14:29 +0100 Subject: [PATCH 03/65] Update config/jail.conf Update to use the new sogo-auth filter --- config/jail.conf | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/config/jail.conf b/config/jail.conf index 1817ebd5..215e5b9a 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -201,6 +201,20 @@ filter = roundcube-auth action = iptables[name=RoundCube, port="http,https"] logpath = /var/log/roundcube/userlogins + +# Monitor SOGo groupware server + +[sogo-iptables] + +enabled = false +filter = sogo-auth +port = http, https +# without proxy this would be: +# port = 20000 + +action = iptables[name=SOGo, port="http,https"] +logpath = /var/log/sogo/sogo.log + # Ban attackers that try to use PHP's URL-fopen() functionality # through GET/POST variables. - Experimental, with more than a year # of usage in production environments. From 35bf84abadfc633e33a36bdcb32586f652fdadf3 Mon Sep 17 00:00:00 2001 From: ArndRa Date: Mon, 11 Feb 2013 08:19:48 -0800 Subject: [PATCH 04/65] Create sogo-auth.conf Regexp works with SOGo 2.0.5 or newer, following new feature implemented here: http://www.sogo.nu/bugs/view.php?id=2229 --- config/filter.d/sogo-auth.conf | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 config/filter.d/sogo-auth.conf diff --git a/config/filter.d/sogo-auth.conf b/config/filter.d/sogo-auth.conf new file mode 100644 index 00000000..e4d29c23 --- /dev/null +++ b/config/filter.d/sogo-auth.conf @@ -0,0 +1,19 @@ +# /etc/fail2ban/filter.d/sogo.conf +# +# Fail2Ban configuration file +# By Arnd Brandes +# SOGo +# + +[Definition] +# Option: failregex +# Filter Ban in /var/log/sogo/sogo.log +# Note: the error log may contain multiple hosts, whereas the first one +# is the client and all others are poxys. We match the first one, only + +failregex = Login from '.*' for user '.*' might not have worked + +# Option: ignoreregex +# Notes.: regex to ignore. If this regex matches, the line is ignored. +# Values: TEXT +# From 6cd358ee95dbeef5a07af25ee028df5cfe543731 Mon Sep 17 00:00:00 2001 From: ArndRa Date: Tue, 12 Feb 2013 10:45:37 +0100 Subject: [PATCH 05/65] Update config/filter.d/sogo-auth.conf Comment line in the top altered to fit file name. My local file was named differently... --- config/filter.d/sogo-auth.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/filter.d/sogo-auth.conf b/config/filter.d/sogo-auth.conf index e4d29c23..6a1abd3f 100644 --- a/config/filter.d/sogo-auth.conf +++ b/config/filter.d/sogo-auth.conf @@ -1,4 +1,4 @@ -# /etc/fail2ban/filter.d/sogo.conf +# /etc/fail2ban/filter.d/sogo-auth.conf # # Fail2Ban configuration file # By Arnd Brandes From 9ba27353b62bb20ae8aaeed45a274483ad67bd83 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Sun, 17 Feb 2013 17:03:23 -0500 Subject: [PATCH 06/65] NF: allow customization configuration under corresponding .d directories (Closes gh-114) Additional changes: ENH: make basedir for Config's a kwarg to the constructor ENH: improved analysis/reporting on presence/accessibility of config files. Got carried away and forgot about existing work done by Steven Hiscocks in the gh-115 -- will merge it in the next commit for the fair ack of his work Now for any X.conf configuration file we have following ways to provide customizations X.local -- read after .conf (kept for backward compatibility) X.d/ -- directory to contain additional .conf files, sorted alphabetically, e.g X.d/01_enable.conf - to enable the jail X.d/02_custom_port.conf - to change the port X could be a 'jail' or 'fail2ban' or any other configuration file in fail2ban. Mention that all files still must contain the corresponding sections (most of the time duplicating it across all of them). --- client/actionreader.py | 4 +- client/configreader.py | 70 ++++++++++++++++-------- client/fail2banreader.py | 4 +- client/filterreader.py | 4 +- client/jailreader.py | 11 ++-- client/jailsreader.py | 10 ++-- fail2ban-testcases | 4 +- testcases/clientreadertestcase.py | 89 ++++++++++++++++++++++++++++--- 8 files changed, 150 insertions(+), 46 deletions(-) diff --git a/client/actionreader.py b/client/actionreader.py index 581a1b3c..9ad1ef28 100644 --- a/client/actionreader.py +++ b/client/actionreader.py @@ -35,8 +35,8 @@ logSys = logging.getLogger("fail2ban.client.config") class ActionReader(ConfigReader): - def __init__(self, action, name): - ConfigReader.__init__(self) + def __init__(self, action, name, **kwargs): + ConfigReader.__init__(self, **kwargs) self.__file = action[0] self.__cInfo = action[1] self.__name = name diff --git a/client/configreader.py b/client/configreader.py index 063484e8..79c5b47b 100644 --- a/client/configreader.py +++ b/client/configreader.py @@ -27,7 +27,7 @@ __date__ = "$Date$" __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" -import logging, os +import glob, logging, os from configparserinc import SafeConfigParserWithIncludes from ConfigParser import NoOptionError, NoSectionError @@ -35,36 +35,64 @@ from ConfigParser import NoOptionError, NoSectionError logSys = logging.getLogger("fail2ban.client.config") class ConfigReader(SafeConfigParserWithIncludes): + + DEFAULT_BASEDIR = '/etc/fail2ban' - BASE_DIRECTORY = "/etc/fail2ban/" - - def __init__(self): + def __init__(self, basedir=None): SafeConfigParserWithIncludes.__init__(self) + self.setBaseDir(basedir) self.__opts = None - #@staticmethod - def setBaseDir(folderName): - path = folderName.rstrip('/') - ConfigReader.BASE_DIRECTORY = path + '/' - setBaseDir = staticmethod(setBaseDir) - - #@staticmethod - def getBaseDir(): - return ConfigReader.BASE_DIRECTORY - getBaseDir = staticmethod(getBaseDir) + def setBaseDir(self, basedir): + if basedir is None: + basedir = ConfigReader.DEFAULT_BASEDIR # stock system location + if not (os.path.exists(basedir) and os.access(basedir, os.R_OK | os.X_OK)): + raise ValueError("Base configuration directory %s either does not exist " + "or is not accessible" % basedir) + self._basedir = basedir.rstrip('/') + + def getBaseDir(self): + return self._basedir def read(self, filename): - basename = ConfigReader.BASE_DIRECTORY + filename + basename = os.path.join(self._basedir, filename) logSys.debug("Reading " + basename) - bConf = basename + ".conf" - bLocal = basename + ".local" - if os.path.exists(bConf) or os.path.exists(bLocal): - SafeConfigParserWithIncludes.read(self, [bConf, bLocal]) + config_files = [ basename + ".conf", + basename + ".local" ] + + # choose only existing ones + config_files = filter(os.path.exists, config_files) + + # 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.warn("%s exists but not a directory or not accessible" + % 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.warn("%s exists but not accessible - skipping" % f) + + if len(config_files_accessible): + # at least one config exists and accessible + SafeConfigParserWithIncludes.read(self, config_files_accessible) return True else: - logSys.error(bConf + " and " + bLocal + " do not exist") + logSys.error("Found no accessible config files for %r " % filename + + (["", + "among existing ones: " + ', '.join(config_files)][bool(len(config_files))])) + return False - + ## # Read the options. # diff --git a/client/fail2banreader.py b/client/fail2banreader.py index ee097bd6..7115ec79 100644 --- a/client/fail2banreader.py +++ b/client/fail2banreader.py @@ -35,8 +35,8 @@ logSys = logging.getLogger("fail2ban.client.config") class Fail2banReader(ConfigReader): - def __init__(self): - ConfigReader.__init__(self) + def __init__(self, **kwargs): + ConfigReader.__init__(self, **kwargs) def read(self): ConfigReader.read(self, "fail2ban") diff --git a/client/filterreader.py b/client/filterreader.py index b7a72f9c..7dba3579 100644 --- a/client/filterreader.py +++ b/client/filterreader.py @@ -35,8 +35,8 @@ logSys = logging.getLogger("fail2ban.client.config") class FilterReader(ConfigReader): - def __init__(self, fileName, name): - ConfigReader.__init__(self) + def __init__(self, fileName, name, **kwargs): + ConfigReader.__init__(self, **kwargs) self.__file = fileName self.__name = name diff --git a/client/jailreader.py b/client/jailreader.py index f66dc010..ec73ce46 100644 --- a/client/jailreader.py +++ b/client/jailreader.py @@ -40,8 +40,8 @@ class JailReader(ConfigReader): actionCRE = re.compile("^((?:\w|-|_|\.)+)(?:\[(.*)\])?$") - def __init__(self, name): - ConfigReader.__init__(self) + def __init__(self, name, **kwargs): + ConfigReader.__init__(self, **kwargs) self.__name = name self.__filter = None self.__actions = list() @@ -53,7 +53,7 @@ class JailReader(ConfigReader): return self.__name def read(self): - ConfigReader.read(self, "jail") + return ConfigReader.read(self, "jail") def isEnabled(self): return self.__opts["enabled"] @@ -75,7 +75,8 @@ class JailReader(ConfigReader): if self.isEnabled(): # Read filter - self.__filter = FilterReader(self.__opts["filter"], self.__name) + self.__filter = FilterReader(self.__opts["filter"], self.__name, + basedir=self.getBaseDir()) ret = self.__filter.read() if ret: self.__filter.getOptions(self.__opts) @@ -87,7 +88,7 @@ class JailReader(ConfigReader): for act in self.__opts["action"].split('\n'): try: splitAct = JailReader.splitAction(act) - action = ActionReader(splitAct, self.__name) + action = ActionReader(splitAct, self.__name, basedir=self.getBaseDir()) ret = action.read() if ret: action.getOptions(self.__opts) diff --git a/client/jailsreader.py b/client/jailsreader.py index bedc5a3c..e1b8efa3 100644 --- a/client/jailsreader.py +++ b/client/jailsreader.py @@ -36,12 +36,12 @@ logSys = logging.getLogger("fail2ban.client.config") class JailsReader(ConfigReader): - def __init__(self): - ConfigReader.__init__(self) + def __init__(self, **kwargs): + ConfigReader.__init__(self, **kwargs) self.__jails = list() def read(self): - ConfigReader.read(self, "jail") + return ConfigReader.read(self, "jail") def getOptions(self, section = None): opts = [] @@ -49,7 +49,7 @@ class JailsReader(ConfigReader): if section: # Get the options of a specific jail. - jail = JailReader(section) + jail = JailReader(section, basedir=self.getBaseDir()) jail.read() ret = jail.getOptions() if ret: @@ -62,7 +62,7 @@ class JailsReader(ConfigReader): else: # Get the options of all jails. for sec in self.sections(): - jail = JailReader(sec) + jail = JailReader(sec, basedir=self.getBaseDir()) jail.read() ret = jail.getOptions() if ret: diff --git a/fail2ban-testcases b/fail2ban-testcases index 99fefd57..ff94cfad 100755 --- a/fail2ban-testcases +++ b/fail2ban-testcases @@ -130,8 +130,10 @@ tests.addTest(unittest.makeSuite(actiontestcase.ExecuteAction)) tests.addTest(unittest.makeSuite(failmanagertestcase.AddFailure)) # BanManager tests.addTest(unittest.makeSuite(banmanagertestcase.AddFailure)) -# ClientReader +# ClientReaders +tests.addTest(unittest.makeSuite(clientreadertestcase.ConfigReaderTest)) tests.addTest(unittest.makeSuite(clientreadertestcase.JailReaderTest)) +tests.addTest(unittest.makeSuite(clientreadertestcase.JailsReaderTest)) # Filter if not opts.no_network: diff --git a/testcases/clientreadertestcase.py b/testcases/clientreadertestcase.py index 83121345..55fb010d 100644 --- a/testcases/clientreadertestcase.py +++ b/testcases/clientreadertestcase.py @@ -27,20 +27,93 @@ __date__ = "$Date$" __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" -import unittest +import os, shutil, tempfile, unittest +from client.configreader import ConfigReader from client.jailreader import JailReader +from client.jailsreader import JailsReader -class JailReaderTest(unittest.TestCase): +class ConfigReaderTest(unittest.TestCase): def setUp(self): """Call before every test case.""" + self.d = tempfile.mkdtemp(prefix="f2b-temp") + self.c = ConfigReader(basedir=self.d) + def tearDown(self): """Call after every test case.""" + shutil.rmtree(self.d) + + def _write(self, fname, value): + # verify if we don't need to create .d directory + if os.path.sep in fname: + d = os.path.dirname(fname) + d_ = os.path.join(self.d, d) + if not os.path.exists(d_): + os.makedirs(d_) + open("%s/%s" % (self.d, fname), "w").write(""" +[section] +option = %s +""" % value) + + def _remove(self, fname): + os.unlink("%s/%s" % (self.d, fname)) + self.assertTrue(self.c.read('c')) # we still should have some + + + def _getoption(self): + self.assertTrue(self.c.read('c')) # we got some now + return self.c.getOptions('section', [("int", 'option')])['option'] + + def testOptionalDotDDir(self): + self.assertFalse(self.c.read('c')) # nothing is there yet + self._write("c.conf", "1") + self.assertEqual(self._getoption(), 1) + self._write("c.conf", "2") # overwrite + self.assertEqual(self._getoption(), 2) + self._write("c.local", "3") # add override in .local + self.assertEqual(self._getoption(), 3) + self._write("c.d/98.conf", "998") # add 1st override in .d/ + self.assertEqual(self._getoption(), 998) + self._write("c.d/90.conf", "990") # add previously sorted override in .d/ + self.assertEqual(self._getoption(), 998) # should stay the same + self._write("c.d/99.conf", "999") # now override in a way without sorting we possibly get a failure + self.assertEqual(self._getoption(), 999) + self._remove("c.d/99.conf") + self.assertEqual(self._getoption(), 998) + self._remove("c.d/98.conf") + self.assertEqual(self._getoption(), 990) + self._remove("c.d/90.conf") + self.assertEqual(self._getoption(), 3) + self._remove("c.conf") # we allow to stay without .conf + self.assertEqual(self._getoption(), 3) + self._write("c.conf", "1") + self._remove("c.local") + self.assertEqual(self._getoption(), 1) + + +class JailReaderTest(unittest.TestCase): + + def testStockSSHJail(self): + jail = JailReader('ssh-iptables', basedir='config') # we are running tests from root project dir atm + self.assertTrue(jail.read()) + self.assertTrue(jail.getOptions()) + self.assertFalse(jail.isEnabled()) + self.assertEqual(jail.getName(), 'ssh-iptables') + + +class JailsReaderTest(unittest.TestCase): + + def testProvidingBadBasedir(self): + if not os.path.exists('/XXX'): + self.assertRaises(ValueError, JailsReader, basedir='/XXX') + + def testReadStockJailConf(self): + jails = JailsReader(basedir='config') # we are running tests from root project dir atm + self.assertTrue(jails.read()) # opens fine + self.assertTrue(jails.getOptions()) # reads fine + comm_commands = jails.convert() + # by default None of the jails is enabled and we get no + # commands to communicate to the server + self.assertEqual(comm_commands, []) - def testSplitAction(self): - action = "mail-whois[name=SSH]" - expected = ['mail-whois', {'name': 'SSH'}] - result = JailReader.splitAction(action) - self.assertEquals(expected, result) - From 2312b1d950826bebcedc0fef937afa9fb76f0602 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Sun, 17 Feb 2013 17:19:09 -0500 Subject: [PATCH 07/65] ENH: made log messages while parsing files more informative + test for inaccessible file (Closes: gh-24) --- client/configreader.py | 4 ++-- testcases/clientreadertestcase.py | 14 +++++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/client/configreader.py b/client/configreader.py index 79c5b47b..e1a2b69f 100644 --- a/client/configreader.py +++ b/client/configreader.py @@ -56,7 +56,7 @@ class ConfigReader(SafeConfigParserWithIncludes): def read(self, filename): basename = os.path.join(self._basedir, filename) - logSys.debug("Reading " + basename) + logSys.debug("Reading configs for %s under %s " % (basename, self._basedir)) config_files = [ basename + ".conf", basename + ".local" ] @@ -88,7 +88,7 @@ class ConfigReader(SafeConfigParserWithIncludes): return True else: logSys.error("Found no accessible config files for %r " % filename - + (["", + + (["under %s" % self.getBaseDir(), "among existing ones: " + ', '.join(config_files)][bool(len(config_files))])) return False diff --git a/testcases/clientreadertestcase.py b/testcases/clientreadertestcase.py index 55fb010d..389220ee 100644 --- a/testcases/clientreadertestcase.py +++ b/testcases/clientreadertestcase.py @@ -39,7 +39,6 @@ class ConfigReaderTest(unittest.TestCase): self.d = tempfile.mkdtemp(prefix="f2b-temp") self.c = ConfigReader(basedir=self.d) - def tearDown(self): """Call after every test case.""" shutil.rmtree(self.d) @@ -61,10 +60,19 @@ option = %s self.assertTrue(self.c.read('c')) # we still should have some - def _getoption(self): - self.assertTrue(self.c.read('c')) # we got some now + def _getoption(self, f='c'): + self.assertTrue(self.c.read(f)) # we got some now return self.c.getOptions('section', [("int", 'option')])['option'] + + def testInaccessibleFile(self): + f = os.path.join(self.d, "d.conf") # inaccessible file + self._write('d.conf', 0) + self.assertEqual(self._getoption('d'), 0) + os.chmod(f, 0) + self.assertFalse(self.c.read('d')) # should not be readable BUT present + + def testOptionalDotDDir(self): self.assertFalse(self.c.read('c')) # nothing is there yet self._write("c.conf", "1") From ec3080cba599832506723f1492de1150d19d2ca5 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Sun, 17 Feb 2013 17:21:03 -0500 Subject: [PATCH 08/65] Reincarnated removed (by mistake) test for SplitAction --- testcases/clientreadertestcase.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/testcases/clientreadertestcase.py b/testcases/clientreadertestcase.py index 389220ee..d581a81f 100644 --- a/testcases/clientreadertestcase.py +++ b/testcases/clientreadertestcase.py @@ -109,6 +109,11 @@ class JailReaderTest(unittest.TestCase): self.assertFalse(jail.isEnabled()) self.assertEqual(jail.getName(), 'ssh-iptables') + def testSplitAction(self): + action = "mail-whois[name=SSH]" + expected = ['mail-whois', {'name': 'SSH'}] + result = JailReader.splitAction(action) + self.assertEquals(expected, result) class JailsReaderTest(unittest.TestCase): From d17b415371d14f00c9b24fc3672733b6666fe345 Mon Sep 17 00:00:00 2001 From: sebres Date: Tue, 5 Mar 2013 00:02:39 +0100 Subject: [PATCH 09/65] invalid date recognition, irregular because of sorting template list (sometimes not reproducible by fail2ban-regex, cause will be not sorted) date in following log line (from nginx) will be wrong detected: 2012/10/11 02:37:17 [error] 18434#0: *947 user "test" was not found in "/www/...", client: 192.168.1.5, ... sometimes it is [correct] - 2012/10/11 (%Y/%m/%d) = 1349919861.71 sometimes it is [invalid] - 12/10/11 (%d/%m/%y) = 1349915838.06 and older as now - 1800 seconds (therefore will be not found) solution: regular expression fixed, cause date in log used always after non symbol (\W) character, so r"\d{2}/\d{2}/\d{2}" will be r"(? Date: Mon, 11 Mar 2013 13:52:31 +0100 Subject: [PATCH 10/65] invalid date recognition, irregular because of sorting template list, now via setRegex --- server/datedetector.py | 26 +++++++++++++------------- server/datetemplate.py | 7 +++++-- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/server/datedetector.py b/server/datedetector.py index 6e484449..c013d551 100644 --- a/server/datedetector.py +++ b/server/datedetector.py @@ -55,80 +55,80 @@ class DateDetector: # standard template = DateStrptime() template.setName("MONTH Day Hour:Minute:Second") - template.setRegex("(? Date: Mon, 11 Mar 2013 17:22:57 +0000 Subject: [PATCH 11/65] Added testcase for csocket and asyncserver --- fail2ban-testcases | 3 ++ testcases/sockettestcase.py | 82 +++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 testcases/sockettestcase.py diff --git a/fail2ban-testcases b/fail2ban-testcases index ed341385..2ab59f6a 100755 --- a/fail2ban-testcases +++ b/fail2ban-testcases @@ -35,6 +35,7 @@ from testcases import filtertestcase from testcases import servertestcase from testcases import datedetectortestcase from testcases import actiontestcase +from testcases import sockettestcase from server.mytime import MyTime from optparse import OptionParser, Option @@ -132,6 +133,8 @@ tests.addTest(unittest.makeSuite(failmanagertestcase.AddFailure)) tests.addTest(unittest.makeSuite(banmanagertestcase.AddFailure)) # ClientReader tests.addTest(unittest.makeSuite(clientreadertestcase.JailReaderTest)) +# CSocket and AsyncServer +tests.addTest(unittest.makeSuite(sockettestcase.Socket)) # Filter if not opts.no_network: diff --git a/testcases/sockettestcase.py b/testcases/sockettestcase.py new file mode 100644 index 00000000..b7258c0f --- /dev/null +++ b/testcases/sockettestcase.py @@ -0,0 +1,82 @@ +# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*- +# vi: set ft=python sts=4 ts=4 sw=4 noet : + +# This file is part of Fail2Ban. +# +# Fail2Ban is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Fail2Ban is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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" + +import unittest, time, tempfile, os, threading +from server.asyncserver import AsyncServer, AsyncServerException +from client.csocket import CSocket + +class Socket(unittest.TestCase): + + def setUp(self): + """Call before every test case.""" + self.server = AsyncServer(self) + sock_fd, sock_name = tempfile.mkstemp('fail2ban.sock', 'socket') + os.close(sock_fd) + os.remove(sock_name) + self.sock_name = sock_name + + def tearDown(self): + """Call after every test case.""" + + @staticmethod + def proceed(message): + """Test transmitter proceed method which just returns first arg""" + return message + + def testSocket(self): + serverThread = threading.Thread( + target=self.server.start, args=(self.sock_name, False)) + serverThread.daemon = True + serverThread.start() + time.sleep(1) + + client = CSocket(self.sock_name) + testMessage = ["A", "test", "message"] + self.assertEqual(client.send(testMessage), testMessage) + + self.server.stop() + serverThread.join(1) + self.assertFalse(os.path.exists(self.sock_name)) + + def testSocketForce(self): + open(self.sock_name, 'w').close() # Create sock file + # Try to start without force + self.assertRaises( + AsyncServerException, self.server.start, self.sock_name, False) + + # Try agin with force set + serverThread = threading.Thread( + target=self.server.start, args=(self.sock_name, True)) + serverThread.daemon = True + serverThread.start() + time.sleep(1) + + self.server.stop() + serverThread.join(1) + self.assertFalse(os.path.exists(self.sock_name)) From 2a57a17c860399818e10e18ec62ef765a7cc28d4 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sat, 16 Mar 2013 13:51:23 +1100 Subject: [PATCH 12/65] completed items from TODO list --- TODO | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TODO b/TODO index 933134e9..61bdc093 100644 --- a/TODO +++ b/TODO @@ -16,9 +16,9 @@ Legend: - Run tests though all filters/examples files - (see sshd example file) as unit test -- Removed relative imports +* Removed relative imports -- Cleanup fail2ban-client and fail2ban-server. Move code to server/ and client/ +* Cleanup fail2ban-client and fail2ban-server. Move code to server/ and client/ - Add timeout to external commands (signal alarm, watchdog thread, etc) From a5928cc3cfe558f9731a32015ffbae6619a61fbb Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sat, 16 Mar 2013 13:52:18 +1100 Subject: [PATCH 13/65] ENH: packaging information for RPM builds --- setup.cfg | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/setup.cfg b/setup.cfg index cdc25b29..53e7276a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,3 +3,11 @@ install-purelib=/usr/share/fail2ban [sdist] formats=bztar + +[bdist_rpm] +release = 1 +packager = Yaroslav Halchenko +doc_files = DEVELOP + README + THANKS + doc/run-rootless.txt From 10d4aa16820accaedb313871dd56376142cf7f8c Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sat, 16 Mar 2013 13:53:12 +1100 Subject: [PATCH 14/65] BF: remove non-existant files from setup.py. ENH: add documentation --- setup.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 37086227..784999a2 100755 --- a/setup.py +++ b/setup.py @@ -67,6 +67,9 @@ setup( ), ('/var/run/fail2ban', '' + ), + ('/usr/share/doc/fail2ban', + ['README', 'DEVELOP', 'doc/run-rootless.txt'] ) ] ) @@ -82,13 +85,7 @@ elements = { "/usr/bin/": [ "fail2ban.py" - ], - "/usr/lib/fail2ban/firewall/": - [ - "iptables.py", - "ipfwadm.py", - "ipfw.py" - ], + ], "/usr/lib/fail2ban/": [ "version.py", From c6ac9c2e353ded2d081f6cc93359188293c057f4 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sat, 16 Mar 2013 13:53:51 +1100 Subject: [PATCH 15/65] ENH: git ignore failed patch reminants --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 1e3b2ec4..c2e979e5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ dist *.pyc htmlcov .coverage +*.orig +*.rej From 3aeb1a9f4e9094b9882b7f7daddbf687161968f6 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sat, 16 Mar 2013 13:57:42 +1100 Subject: [PATCH 16/65] ENH: jail.conf man page --- man/jail.conf.5 | 145 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 man/jail.conf.5 diff --git a/man/jail.conf.5 b/man/jail.conf.5 new file mode 100644 index 00000000..7a841f7d --- /dev/null +++ b/man/jail.conf.5 @@ -0,0 +1,145 @@ +.TH JAIL.CONF.5 "fail2ban" "jail.conf(5)" +.SH NAME +jail.conf \- configuration for the fail2ban server +.SH SYNOPSIS +.B jail.conf / jail.local + +.B action.d/*.conf action.d/*.local + +.B filter.d/*.conf filter.d/*.local +.SH DESCRIPTION +Fail2ban has three configuration file types. Action files are the commands for banning and unbanning IP address, +Filter files tell fail2ban how authentication failures occur, and Jail configurations that map the two together. + +There are *.conf files that are distributed by fail2ban and *.local file that contain user overwritten files. +It is recommend that *.conf files are not changed. *.local files are where users overwrite their default settings. +For instance if you require the default [ssh-iptables-ipset] jail create a jail.local with The jail.conf file is + the configuration for the fail2ban server. Only overwride the settings you need to change and the rest come from +the default *.conf file. + +[ssh-iptables-ipset] + +enabled = true + +.SH DEFAULT +The following options are applicable to all jails. Their meaning is in the default jail.conf file. +.TP +\fBignoreip\fR +.TP +\fBbantime\fR +.TP +\fBfindtime\fR +.TP +\fBmaxretry\fR +.TP +\fBbackend\fR +.TP +\fBusedns\fR +.TP +.SH ACTION FILES +Action files specify which commands are executed to ban an IP address. They are located in /etc/fail2ban/action.d. + +Like jail.conf files if you desire local changes create a [actionname].local file in the /etc/fail2ban/action.d directory +and override the required settings. + +The action files are ini files that have two sections, \fBdefination\fR and \fBinit\fR . + +The [init] section allows for settings relevant to the action. In jail.conf/jail.local these can be overwritten for a particular jail. + +The commands are specified in the [defination] section as below.. + +The commands are executed through a system shell so shell redirection and process control is allowed. The commands should +return 0 to indicate success which will prevent fail2ban attempting to retry (up to \fBmaxretry\fR times to ban the IP again. + +The following action parameters are in the [defination] section. +.TP +\fBactionstart\fR +This is the commands that are executed when the jail starts. +.TP +\fBactionstop\fR +This is the commands that are executed when the jail stops. +.TP +\fBactioncheck\fR +This is the command that is before each other action. It checks to see if the environment is ok. +.TP +\fBactionban\fR +This is the command that bans the IP address. +.TP +\fBactionunban\fR +This is the command that unbans the IP address after \fBbantime\fR. + +Tags are in <>. All the elements of [init] are tags that are replaced in all action commands. Tags can be added by the +\fBfail2ban-client\fR using the setctag command. + +More that one command is allowed. Each command needs to be on a new line and indented with whitespace with no blank lines. The following defines +two commands to be executed. + +actionban = iptables -I fail2ban- --source -j DROP + + echo ip=, match=, time=