From f9b78ba92798da40509c5743c1d15e13d6a7f3ec Mon Sep 17 00:00:00 2001 From: Michael Gebetsroither <michael@mgeb.org> Date: Thu, 3 Jan 2013 18:46:31 +0100 Subject: [PATCH 01/26] add support for blocking through blackhole routes --- config/action.d/route.conf | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 config/action.d/route.conf diff --git a/config/action.d/route.conf b/config/action.d/route.conf new file mode 100644 index 00000000..2d11c700 --- /dev/null +++ b/config/action.d/route.conf @@ -0,0 +1,19 @@ +# Fail2Ban configuration file +# +# Author: Michael Gebetsroither +# +# This is for blocking whole hosts through blackhole routes. +# +# PRO: +# - Works on all kernel versions and as no compatibility problems (back to debian lenny and WAY further). +# - It's FAST for very large numbers of blocked ips. +# - It's FAST because it Blocks traffic before it enters common iptables chains used for filtering. +# - It's per host, ideal as action against ssh password bruteforcing to block further attack attempts. +# - No additional software required beside iproute/iproute2 +# +# CON: +# - Blocking is per IP and NOT per service, but ideal as action against ssh password bruteforcing hosts + +[Definition] +actionban = ip route add blackhole <ip> +actionunban = ip route del blackhole <ip> From 03433f79cd52a87c3fd7309e7fe9967076bbb866 Mon Sep 17 00:00:00 2001 From: Michael Gebetsroither <michael@mgeb.org> Date: Fri, 4 Jan 2013 16:09:04 +0100 Subject: [PATCH 02/26] add example jail.conf for blocking through blackhole routes for ssh --- config/jail.conf | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/config/jail.conf b/config/jail.conf index 3f2425b4..fb9f9ca0 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -101,6 +101,17 @@ action = hostsdeny ignoreregex = for myuser from logpath = /var/log/sshd.log +# Here we use blackhole routes for not requiring any additional kernel support +# to store large volumes of banned IPs + +[ssh-route] + +enabled = false +filter = sshd +action = route +logpath = /var/log/sshd.log +maxretry = 5 + # Here we use a combination of Netfilter/Iptables and IPsets # for storing large volumes of banned IPs # From 6004fe7a9449caf2aeb135cb3b7e76efa52b2ad7 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko <debian@onerussian.com> Date: Mon, 11 Feb 2013 16:17:52 -0500 Subject: [PATCH 03/26] just trailing spaces in setup.py --- setup.py | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/setup.py b/setup.py index 53b03f0f..37086227 100755 --- a/setup.py +++ b/setup.py @@ -36,33 +36,33 @@ to reject the IP address or executes user defined commands.''' setup( - name = "fail2ban", - version = version, - description = "Ban IPs that make too many password failure", - long_description = longdesc, - author = "Cyril Jaquier", - author_email = "cyril.jaquier@fail2ban.org", - url = "http://www.fail2ban.org", - license = "GPL", - platforms = "Posix", + name = "fail2ban", + version = version, + description = "Ban IPs that make too many password failure", + long_description = longdesc, + author = "Cyril Jaquier", + author_email = "cyril.jaquier@fail2ban.org", + url = "http://www.fail2ban.org", + license = "GPL", + platforms = "Posix", scripts = [ - 'fail2ban-client', - 'fail2ban-server', + 'fail2ban-client', + 'fail2ban-server', 'fail2ban-regex' - ], + ], packages = [ - 'common', - 'client', + 'common', + 'client', 'server' - ], + ], data_files = [ - ('/etc/fail2ban', + ('/etc/fail2ban', glob("config/*.conf") - ), - ('/etc/fail2ban/filter.d', + ), + ('/etc/fail2ban/filter.d', glob("config/filter.d/*.conf") - ), - ('/etc/fail2ban/action.d', + ), + ('/etc/fail2ban/action.d', glob("config/action.d/*.conf") ), ('/var/run/fail2ban', @@ -78,20 +78,20 @@ elements = { "/etc/": [ "fail2ban.conf" - ], + ], "/usr/bin/": [ "fail2ban.py" - ], + ], "/usr/lib/fail2ban/firewall/": [ - "iptables.py", - "ipfwadm.py", + "iptables.py", + "ipfwadm.py", "ipfw.py" ], "/usr/lib/fail2ban/": [ - "version.py", + "version.py", "protocol.py" ] } From 8cf006827e6f7def17aef82f7a792f66747be8ca Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko <debian@onerussian.com> Date: Tue, 12 Feb 2013 08:48:05 -0500 Subject: [PATCH 04/26] BF: remove path from grep call in sendmail-whois-lines.conf Closes: gh-118 --- config/action.d/sendmail-whois-lines.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/action.d/sendmail-whois-lines.conf b/config/action.d/sendmail-whois-lines.conf index d1e6e40f..b84440d0 100644 --- a/config/action.d/sendmail-whois-lines.conf +++ b/config/action.d/sendmail-whois-lines.conf @@ -57,7 +57,7 @@ actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> Here are more information about <ip>:\n `/usr/bin/whois <ip>`\n\n Lines containing IP:<ip> in <logpath>\n - `/bin/grep '\<<ip>\>' <logpath>`\n\n + `grep '\<<ip>\>' <logpath>`\n\n Regards,\n Fail2Ban" | /usr/sbin/sendmail -f <sender> <dest> From 47b1ee39d8e5e918afaea40f361b3932a2b8616e Mon Sep 17 00:00:00 2001 From: Daniel Black <grooverdan@users.sourceforge.net> Date: Sun, 17 Feb 2013 12:37:34 +1100 Subject: [PATCH 05/26] add blocking type --- config/action.d/route.conf | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/config/action.d/route.conf b/config/action.d/route.conf index 2d11c700..ec940b74 100644 --- a/config/action.d/route.conf +++ b/config/action.d/route.conf @@ -15,5 +15,11 @@ # - Blocking is per IP and NOT per service, but ideal as action against ssh password bruteforcing hosts [Definition] -actionban = ip route add blackhole <ip> -actionunban = ip route del blackhole <ip> +actionban = ip route add <type> <ip> +actionunban = ip route del <type> <ip> + +# Type of blocking +# +# Type can be blackhole, unreachable and prohibit. Unreachable and prohibit correspond to the ICMP reject messages. + +type = blackhole From ce3ab34dd8bc0181fbb68c7fc8b9ed18e35cf5f3 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks <steven@hiscocks.me.uk> Date: Sun, 17 Feb 2013 22:14:01 +0000 Subject: [PATCH 06/26] Added ability to specify PID file --- client/fail2banreader.py | 3 ++- config/fail2ban.conf | 7 +++++++ fail2ban-client | 18 ++++++++++++++---- fail2ban-server | 10 ++++++++-- server/server.py | 12 +++++------- 5 files changed, 36 insertions(+), 14 deletions(-) diff --git a/client/fail2banreader.py b/client/fail2banreader.py index ee097bd6..6954b3bf 100644 --- a/client/fail2banreader.py +++ b/client/fail2banreader.py @@ -42,7 +42,8 @@ class Fail2banReader(ConfigReader): ConfigReader.read(self, "fail2ban") def getEarlyOptions(self): - opts = [["string", "socket", "/tmp/fail2ban.sock"]] + opts = [["string", "socket", "/tmp/fail2ban.sock"], + ["string", "pidfile", "/var/run/fail2ban/fail2ban.pid"]] return ConfigReader.getOptions(self, "Definition", opts) def getOptions(self): diff --git a/config/fail2ban.conf b/config/fail2ban.conf index f2f1b215..e759513b 100644 --- a/config/fail2ban.conf +++ b/config/fail2ban.conf @@ -40,3 +40,10 @@ logtarget = /var/log/fail2ban.log # 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 +# +pidfile = /var/run/fail2ban/fail2ban.pid + diff --git a/fail2ban-client b/fail2ban-client index 13d018e6..595144ef 100755 --- a/fail2ban-client +++ b/fail2ban-client @@ -62,6 +62,7 @@ class Fail2banClient: self.__conf["verbose"] = 1 self.__conf["interactive"] = False self.__conf["socket"] = None + self.__conf["pidfile"] = None def dispVersion(self): print "Fail2Ban v" + version @@ -84,6 +85,7 @@ class Fail2banClient: print "Options:" print " -c <DIR> configuration directory" print " -s <FILE> socket path" + print " -p <FILE> pidfile path" print " -d dump configuration. For debugging" print " -i interactive mode" print " -v increase verbosity" @@ -119,6 +121,8 @@ class Fail2banClient: self.__conf["conf"] = opt[1] elif opt[0] == "-s": self.__conf["socket"] = opt[1] + elif opt[0] == "-p": + self.__conf["pidfile"] = opt[1] elif opt[0] == "-d": self.__conf["dump"] = True elif opt[0] == "-v": @@ -183,6 +187,7 @@ class Fail2banClient: return False # Start the server self.__startServerAsync(self.__conf["socket"], + self.__conf["pidfile"], self.__conf["force"]) try: # Wait for the server to start @@ -231,7 +236,7 @@ class Fail2banClient: # # Start the Fail2ban server in daemon mode. - def __startServerAsync(self, socket, force = False): + def __startServerAsync(self, socket, pidfile, force = False): # Forks the current process. pid = os.fork() if pid == 0: @@ -242,6 +247,9 @@ class Fail2banClient: # Set the socket path. args.append("-s") args.append(socket) + # Set the pidfile + args.append("-p") + args.append(pidfile) # Force the execution if needed. if force: args.append("-x") @@ -297,7 +305,7 @@ class Fail2banClient: # Reads the command line options. try: - cmdOpts = 'hc:s:xdviqV' + cmdOpts = 'hc:s:p:xdviqV' cmdLongOpts = ['help', 'version'] optList, args = getopt.getopt(self.__argv[1:], cmdOpts, cmdLongOpts) except getopt.GetoptError: @@ -328,9 +336,11 @@ class Fail2banClient: # Set socket path self.__configurator.readEarly() - socket = self.__configurator.getEarlyOptions() + conf = self.__configurator.getEarlyOptions() if self.__conf["socket"] == None: - self.__conf["socket"] = socket["socket"] + self.__conf["socket"] = conf["socket"] + if self.__conf["pidfile"] == None: + self.__conf["pidfile"] = conf["pidfile"] logSys.info("Using socket file " + self.__conf["socket"]) if self.__conf["dump"]: diff --git a/fail2ban-server b/fail2ban-server index 0f3410c9..81db58bd 100755 --- a/fail2ban-server +++ b/fail2ban-server @@ -54,6 +54,7 @@ class Fail2banServer: self.__conf["background"] = True self.__conf["force"] = False self.__conf["socket"] = "/var/run/fail2ban/fail2ban.sock" + self.__conf["pidfile"] = "/var/run/fail2ban/fail2ban.pid" def dispVersion(self): print "Fail2Ban v" + version @@ -81,6 +82,7 @@ class Fail2banServer: print " -b start in background" print " -f start in foreground" print " -s <FILE> socket path" + print " -p <FILE> pidfile path" print " -x force execution of the server (remove socket file)" print " -h, --help display this help message" print " -V, --version print the version" @@ -97,6 +99,8 @@ class Fail2banServer: self.__conf["background"] = False if opt[0] == "-s": self.__conf["socket"] = opt[1] + if opt[0] == "-p": + self.__conf["pidfile"] = opt[1] if opt[0] == "-x": self.__conf["force"] = True if opt[0] in ["-h", "--help"]: @@ -112,7 +116,7 @@ class Fail2banServer: # Reads the command line options. try: - cmdOpts = 'bfs:xhV' + cmdOpts = 'bfs:p:xhV' cmdLongOpts = ['help', 'version'] optList, args = getopt.getopt(self.__argv[1:], cmdOpts, cmdLongOpts) except getopt.GetoptError: @@ -123,7 +127,9 @@ class Fail2banServer: try: self.__server = Server(self.__conf["background"]) - self.__server.start(self.__conf["socket"], self.__conf["force"]) + self.__server.start(self.__conf["socket"], + self.__conf["pidfile"], + self.__conf["force"]) return True except Exception, e: logSys.exception(e) diff --git a/server/server.py b/server/server.py index d9532be2..3889c491 100644 --- a/server/server.py +++ b/server/server.py @@ -40,8 +40,6 @@ logSys = logging.getLogger("fail2ban.server") class Server: - PID_FILE = "/var/run/fail2ban/fail2ban.pid" - def __init__(self, daemon = False): self.__loggingLock = Lock() self.__lock = RLock() @@ -59,7 +57,7 @@ class Server: logSys.debug("Caught signal %d. Exiting" % signum) self.quit() - def start(self, sock, force = False): + def start(self, sock, pidfile, force = False): logSys.info("Starting Fail2ban v" + version.version) # Install signal handlers @@ -79,8 +77,8 @@ class Server: # Creates a PID file. try: - logSys.debug("Creating PID file %s" % Server.PID_FILE) - pidFile = open(Server.PID_FILE, 'w') + logSys.debug("Creating PID file %s" % pidfile) + pidFile = open(pidfile, 'w') pidFile.write("%s\n" % os.getpid()) pidFile.close() except IOError, e: @@ -94,8 +92,8 @@ class Server: logSys.error("Could not start server: %s", e) # Removes the PID file. try: - logSys.debug("Remove PID file %s" % Server.PID_FILE) - os.remove(Server.PID_FILE) + logSys.debug("Remove PID file %s" % pidfile) + os.remove(pidfile) except OSError, e: logSys.error("Unable to remove PID file: %s" % e) logSys.info("Exiting Fail2ban") From 40c5a2d996ac16f0690c2a8b8f0b95979277d4ee Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko <debian@onerussian.com> Date: Mon, 18 Feb 2013 23:08:44 -0500 Subject: [PATCH 07/26] ENH: adding more of diagnostic messages into -client while starting the daemon --- fail2ban-client | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/fail2ban-client b/fail2ban-client index 595144ef..7ee4a47c 100755 --- a/fail2ban-client +++ b/fail2ban-client @@ -134,11 +134,11 @@ class Fail2banClient: elif opt[0] == "-i": self.__conf["interactive"] = True elif opt[0] in ["-h", "--help"]: - self.dispUsage() - sys.exit(0) - elif opt[0] in ["-V", "--version"]: - self.dispVersion() - sys.exit(0) + self.dispUsage() + sys.exit(0) + elif opt[0] in ["-V", "--version"]: + self.dispVersion() + sys.exit(0) def __ping(self): return self.__processCmd([["ping"]], False) @@ -185,6 +185,18 @@ class Fail2banClient: # Do not continue if configuration is not 100% valid if not ret: return False + # verify that directory for the socket file exists + socket_dir = os.path.dirname(self.__conf["socket"]) + if not os.path.exists(socket_dir): + logSys.error( + "There is no directory %s to contain the socket file %s." + % (socket_dir, self.__conf["socket"])) + return False + if not os.access(socket_dir, os.W_OK | os.X_OK): + logSys.error( + "Directory %s exists but not accessible for writing" + % (socket_dir,)) + return False # Start the server self.__startServerAsync(self.__conf["socket"], self.__conf["pidfile"], @@ -196,10 +208,10 @@ class Fail2banClient: self.__processCmd(self.__stream, False) return True except ServerExecutionException: - logSys.error("Could not start server. Maybe an old " + - "socket file is still present. Try to " + - "remove " + self.__conf["socket"] + ". If " + - "you used fail2ban-client to start the " + + logSys.error("Could not start server. Maybe an old " + "socket file is still present. Try to " + "remove " + self.__conf["socket"] + ". If " + "you used fail2ban-client to start the " "server, adding the -x option will do it") return False elif len(cmd) == 1 and cmd[0] == "reload": @@ -256,16 +268,17 @@ class Fail2banClient: try: # Use the current directory. exe = os.path.abspath(os.path.join(sys.path[0], self.SERVER)) + logSys.debug("Starting %r with args %r" % (exe, args)) os.execv(exe, args) except OSError: try: # Use the PATH env. + logSys.warning("Initial start attempt failed. Starting %r with the same args" % (self.SERVER,)) os.execvp(self.SERVER, args) except OSError: - print "Could not find %s" % self.SERVER + logSys.error("Could not start %s" % self.SERVER) os.exit(-1) - - + def __waitOnServer(self): # Wait for the server to start cnt = 0 From 088e40c48192caed3a0c415e09211cd08c7c85d0 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks <steven@hiscocks.me.uk> Date: Wed, 20 Feb 2013 23:14:42 +0000 Subject: [PATCH 08/26] Rewrite and enable server testcase for Transmitter --- fail2ban-testcases | 2 +- testcases/servertestcase.py | 449 ++++++++++++++++++++++++++++++------ 2 files changed, 381 insertions(+), 70 deletions(-) diff --git a/fail2ban-testcases b/fail2ban-testcases index 99fefd57..d2bbfed1 100755 --- a/fail2ban-testcases +++ b/fail2ban-testcases @@ -124,7 +124,7 @@ else: # Server #tests.addTest(unittest.makeSuite(servertestcase.StartStop)) -#tests.addTest(unittest.makeSuite(servertestcase.Transmitter)) +tests.addTest(unittest.makeSuite(servertestcase.Transmitter)) tests.addTest(unittest.makeSuite(actiontestcase.ExecuteAction)) # FailManager tests.addTest(unittest.makeSuite(failmanagertestcase.AddFailure)) diff --git a/testcases/servertestcase.py b/testcases/servertestcase.py index 54eac444..47d4ef0b 100644 --- a/testcases/servertestcase.py +++ b/testcases/servertestcase.py @@ -27,7 +27,7 @@ __date__ = "$Date$" __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" -import unittest, socket, time +import unittest, socket, time, tempfile, os from server.server import Server class StartStop(unittest.TestCase): @@ -55,76 +55,387 @@ class Transmitter(unittest.TestCase): def setUp(self): """Call before every test case.""" self.__server = Server() + self.__transm = self.__server._Server__transm + self.__server.setLogTarget("/dev/null") self.__server.setLogLevel(0) - self.__server.start(False) + sock_fd, sock_name = tempfile.mkstemp('fail2ban.sock', 'transmitter') + os.close(sock_fd) + pidfile_fd, pidfile_name = tempfile.mkstemp( + 'fail2ban.pid', 'transmitter') + os.close(pidfile_fd) + self.__server.start(sock_name, pidfile_name, force=False) + self.jailName = "TestJail1" + self.__server.addJail(self.jailName, "auto") def tearDown(self): """Call after every test case.""" self.__server.quit() - - def testSetActionOK(self): - name = "TestCase" - cmdList = [["add", name], - ["set", name, "actionstart", "Action Start"], - ["set", name, "actionstop", "Action Stop"], - ["set", name, "actioncheck", "Action Check"], - ["set", name, "actionban", "Action Ban"], - ["set", name, "actionunban", "Action Unban"], - ["quit"]] - - outList = [(0, name), - (0, 'Action Start'), - (0, 'Action Stop'), - (0, 'Action Check'), - (0, 'Action Ban'), - (0, 'Action Unban'), - (0, None)] - - cnt = 0 - for cmd in cmdList: - self.assertEqual(self.__server.transm.proceed(cmd), outList[cnt]) - cnt += 1 - - def testSetActionNOK(self): - name = "TestCase" - cmdList = [["addd", name], - ["set", name, "test"], - ["prout prout", "Stop"], - ["fail2ban", "sucks"], - ["set"], - ["_/&%", "@*+%&"], - [" quit"]] - - outList = [1, - 1, - 1, - 1, - 1, - 1, - 1] - - cnt = 0 - for cmd in cmdList: - msg = self.__server.transm.proceed(cmd) - self.assertEqual(msg[0], outList[cnt]) - cnt += 1 - - def testJail(self): - name = "TestCase" - cmdList = [["add", name], - ["set", name, "logpath", "testcases/files/testcase01.log"], - ["set", name, "timeregex", "\S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}"], - ["set", name, "timepattern", "%b %d %H:%M:%S"], - ["set", name, "failregex", "Authentication failure"], - ["start", name], - ["stop", name], - ["quit"]] - - for cmd in cmdList: - self.__server.transm.proceed(cmd) - if cmd == ["start", name]: - time.sleep(2) - jail = self.__server.jails[name] - self.assertEqual(jail.getFilter().failManager.size(), 0) - self.assertEqual(jail.getAction().banManager.size(), 2) - + + def setGetTest(self, cmd, inValue, outValue=None, jail=None): + setCmd = ["set", cmd, inValue] + getCmd = ["get", cmd] + if jail is not None: + setCmd.insert(1, jail) + getCmd.insert(1, jail) + if outValue is None: + outValue = inValue + + self.assertEqual(self.__transm.proceed(setCmd), (0, outValue)) + self.assertEqual(self.__transm.proceed(getCmd), (0, outValue)) + + def setGetTestNOK(self, cmd, inValue, jail=None): + setCmd = ["set", cmd, inValue] + getCmd = ["get", cmd] + if jail is not None: + setCmd.insert(1, jail) + getCmd.insert(1, jail) + + # Get initial value before trying invalid value + initValue = self.__transm.proceed(getCmd)[1] + self.assertEqual(self.__transm.proceed(setCmd)[0], 1) + # Check after failed set that value is same as previous + self.assertEqual(self.__transm.proceed(getCmd), (0, initValue)) + + def jailAddDelTest(self, cmd, values, jail): + cmdAdd = "add" + cmd + cmdDel = "del" + cmd + + self.assertEqual( + self.__transm.proceed(["get", jail, cmd]), (0, [])) + for n, value in enumerate(values): + self.assertEqual( + self.__transm.proceed(["set", jail, cmdAdd, value]), + (0, values[:n+1])) + self.assertEqual( + self.__transm.proceed(["get", jail, cmd]), + (0, values[:n+1])) + for n, value in enumerate(values): + self.assertEqual( + self.__transm.proceed(["set", jail, cmdDel, value]), + (0, values[n+1:])) + self.assertEqual( + self.__transm.proceed(["get", jail, cmd]), + (0, values[n+1:])) + + def jailAddDelRegexTest(self, cmd, inValues, outValues, jail): + cmdAdd = "add" + cmd + cmdDel = "del" + cmd + + if outValues is None: + outValues = inValues + + self.assertEqual( + self.__transm.proceed(["get", jail, cmd]), (0, [])) + for n, value in enumerate(inValues): + self.assertEqual( + self.__transm.proceed(["set", jail, cmdAdd, value]), + (0, outValues[:n+1])) + self.assertEqual( + self.__transm.proceed(["get", jail, cmd]), + (0, outValues[:n+1])) + for n, value in enumerate(inValues): + self.assertEqual( + self.__transm.proceed(["set", jail, cmdDel, 0]), # First item + (0, outValues[n+1:])) + self.assertEqual( + self.__transm.proceed(["get", jail, cmd]), + (0, outValues[n+1:])) + + def testPing(self): + self.assertEqual(self.__transm.proceed(["ping"]), (0, "pong")) + + def testSleep(self): + t0 = time.time() + self.assertEqual(self.__transm.proceed(["sleep", "1"]), (0, None)) + t1 = time.time() + # Approx 1 second delay + self.assertAlmostEqual(t1 - t0, 1, places=2) + + def testLogTarget(self): + logTargets = [] + for _ in xrange(3): + tmpFile = tempfile.mkstemp("fail2ban", "transmitter") + logTargets.append(tmpFile[1]) + os.close(tmpFile[0]) + for logTarget in logTargets: + self.setGetTest("logtarget", logTarget) + + # If path is invalid, do not change logtarget + value = "/this/path/should/not/exist" + self.assertEqual( + self.__transm.proceed(["set", "logtarget", value]), + (0, logTarget)) #NOTE: Shouldn't this return 1 + self.assertEqual( + self.__transm.proceed(["get", "logtarget"]), (0, logTargets[-1])) + + self.__transm.proceed(["set", "/dev/null"]) + for logTarget in logTargets: + os.remove(logTarget) + + def testLogLevel(self): + self.setGetTest("loglevel", "4", 4) + self.setGetTest("loglevel", "2", 2) + self.setGetTest("loglevel", "-1", -1) + self.setGetTestNOK("loglevel", "Bird") + + def testAddJail(self): + jail2 = "TestJail2" + jail3 = "TestJail3" + jail4 = "TestJail4" + self.assertEqual( + self.__transm.proceed(["add", jail2, "polling"]), (0, jail2)) + self.assertEqual(self.__transm.proceed(["add", jail3]), (0, jail3)) + self.assertEqual( + self.__transm.proceed(["add", jail4, "invalid backend"])[0], 1) + self.assertEqual( + self.__transm.proceed(["add", jail4, "auto"]), (0, jail4)) + # Duplicate Jail + self.assertEqual( + self.__transm.proceed(["add", self.jailName, "polling"])[0], 1) + # All name is reserved + self.assertEqual( + self.__transm.proceed(["add", "all", "polling"])[0], 1) + + def testJailIdle(self): + self.assertEqual( + self.__transm.proceed(["set", self.jailName, "idle", "on"]), + (0, True)) + self.assertEqual( + self.__transm.proceed(["set", self.jailName, "idle", "off"]), + (0, False)) + self.assertEqual( + self.__transm.proceed(["set", self.jailName, "idle", "CAT"])[0], + 0) #NOTE: Should this return 1 + + def testJailFindTime(self): + self.setGetTest("findtime", "120", 120, jail=self.jailName) + self.setGetTest("findtime", "60", 60, jail=self.jailName) + self.setGetTest("findtime", "-60", -60, jail=self.jailName) + self.setGetTestNOK("findtime", "Dog", jail=self.jailName) + + def testJailBanTime(self): + self.setGetTest("bantime", "600", 600, jail=self.jailName) + self.setGetTest("bantime", "50", 50, jail=self.jailName) + self.setGetTest("bantime", "-50", -50, jail=self.jailName) + self.setGetTestNOK("bantime", "Cat", jail=self.jailName) + + def testJailUseDNS(self): + self.setGetTest("usedns", "yes", jail=self.jailName) + self.setGetTest("usedns", "warn", jail=self.jailName) + self.setGetTest("usedns", "no", jail=self.jailName) + + # Safe default should be "no" + value = "Fish" + self.assertEqual( + self.__transm.proceed(["set", self.jailName, "usedns", value]), + (0, "no")) + + def testJailBanIP(self): + self.__server.startJail(self.jailName) # Jail must be started + + self.assertEqual( + self.__transm.proceed(["set", self.jailName, "banip", "127.0.0.1"]), + (0, "127.0.0.1")) + time.sleep(1) # Give chance to ban + self.assertEqual( + self.__transm.proceed(["set", self.jailName, "banip", "Badger"]), + (0, "Badger")) #NOTE: Is IP address validated? Is DNS Lookup done? + time.sleep(1) # Give chance to ban + # Unban IP + self.assertEqual( + self.__transm.proceed( + ["set", self.jailName, "unbanip", "127.0.0.1"]), + (0, "127.0.0.1")) + # Unban IP which isn't banned + self.assertEqual( + self.__transm.proceed( + ["set", self.jailName, "unbanip", "192.168.1.1"]), + (0, "None")) #NOTE: Should this return 1? + + def testJailMaxRetry(self): + self.setGetTest("maxretry", "5", 5, jail=self.jailName) + self.setGetTest("maxretry", "2", 2, jail=self.jailName) + self.setGetTest("maxretry", "-2", -2, jail=self.jailName) + self.setGetTestNOK("maxretry", "Duck", jail=self.jailName) + + def testJailLogPath(self): + self.jailAddDelTest( + "logpath", + [ + "testcases/files/testcase01.log", + "testcases/files/testcase02.log", + "testcases/files/testcase03.log", + ], + self.jailName + ) + # Try duplicates + value = "testcases/files/testcase04.log" + self.assertEqual( + self.__transm.proceed(["set", self.jailName, "addlogpath", value]), + (0, [value])) + # Will silently ignore duplicate + self.assertEqual( + self.__transm.proceed(["set", self.jailName, "addlogpath", value]), + (0, [value])) + self.assertEqual( + self.__transm.proceed(["get", self.jailName, "logpath"]), + (0, [value])) + self.assertEqual( + self.__transm.proceed(["set", self.jailName, "dellogpath", value]), + (0, [])) + + # Invalid file + value = "this_file_shouldn't_exist" + result = self.__transm.proceed( + ["set", self.jailName, "addlogpath", value]) + self.assertTrue(isinstance(result[1], IOError)) + + def testJailIgnoreIP(self): + self.jailAddDelTest( + "ignoreip", + [ + "127.0.0.1", + "192.168.1.1", + "8.8.8.8", + ], + self.jailName + ) + + # Try duplicates + value = "127.0.0.1" + self.assertEqual( + self.__transm.proceed(["set", self.jailName, "addignoreip", value]), + (0, [value])) + # Will allow duplicate + #NOTE: Should duplicates be allowed, or silent ignore like logpath? + self.assertEqual( + self.__transm.proceed(["set", self.jailName, "addignoreip", value]), + (0, [value, value])) + self.assertEqual( + self.__transm.proceed(["get", self.jailName, "ignoreip"]), + (0, [value, value])) + self.assertEqual( + self.__transm.proceed(["set", self.jailName, "delignoreip", value]), + (0, [value])) + + def testJailRegex(self): + self.jailAddDelRegexTest("failregex", + [ + "user john at <HOST>", + "Admin user login from <HOST>", + "failed attempt from <HOST> again", + ], + [ + "user john at (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)", + "Admin user login from (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)", + "failed attempt from (?:::f{4,6}:)?(?P<host>[\w\-.^_]+) again", + ], + self.jailName + ) + + self.assertEqual( + self.__transm.proceed( + ["set", self.jailName, "addfailregex", "No host regex"]), + (0, [])) #NOTE: Shouldn't this return 1? + self.assertEqual( + self.__transm.proceed( + ["set", self.jailName, "addfailregex", 654])[0], + 1) + + def testJailIgnoreRegex(self): + self.jailAddDelRegexTest("ignoreregex", + [ + "user john", + "Admin user login from <HOST>", + "Dont match me!", + ], + [ + "user john", + "Admin user login from (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)", + "Dont match me!", + ], + self.jailName + ) + + self.assertEqual( + self.__transm.proceed( + ["set", self.jailName, "addignoreregex", 50])[0], + 1) + + def testStatus(self): + jails = [self.jailName] + self.assertEqual(self.__transm.proceed(["status"]), + (0, [('Number of jail', len(jails)), ('Jail list', ", ".join(jails))])) + self.__server.addJail("TestJail2", "auto") + jails.append("TestJail2") + self.assertEqual(self.__transm.proceed(["status"]), + (0, [('Number of jail', len(jails)), ('Jail list', ", ".join(jails))])) + + def testJailStatus(self): + self.assertEqual(self.__transm.proceed(["status", self.jailName]), + (0, + [ + ('filter', [ + ('Currently failed', 0), + ('Total failed', 0), + ('File list', [])] + ), + ('action', [ + ('Currently banned', 0), + ('Total banned', 0), + ('IP list', [])] + ) + ] + ) + ) + + def testAction(self): + action = "TestCaseAction" + cmdList = [ + "actionstart", + "actionstop", + "actioncheck", + "actionban", + "actionunban", + ] + cmdValueList = [ + "Action Start", + "Action Stop", + "Action Check", + "Action Ban", + "Action Unban", + ] + + self.assertEqual( + self.__transm.proceed(["set", self.jailName, "addaction", action]), + (0, action)) + for cmd, value in zip(cmdList, cmdValueList): + self.assertEqual( + self.__transm.proceed( + ["set", self.jailName, cmd, action, value]), + (0, value)) + for cmd, value in zip(cmdList, cmdValueList): + self.assertEqual( + self.__transm.proceed(["get", self.jailName, cmd, action]), + (0, value)) + self.assertEqual( + self.__transm.proceed( + ["set", self.jailName, "setcinfo", action, "KEY", "VALUE"]), + (0, "VALUE")) + #TODO: Implement below in server.transmitter? + """ + self.assertEqual( + self.__transm.proceed( + ["get", self.jailName, "cinfo", action, "KEY"]), + (0, "VALUE")) + """ + self.assertEqual( + self.__transm.proceed( + ["set", self.jailName, "delcinfo", action, "KEY"]), + (0, None)) + #FIXME: This is broken in server.transmitter (value not defined) + """ + self.assertEqual( + self.__transm.proceed(["set", self.jailName, "delaction", action]), + (0, None)) + """ From b6a68f5138c12e5f1374d76ac76f8f43bc814bfc Mon Sep 17 00:00:00 2001 From: Steven Hiscocks <steven@hiscocks.me.uk> Date: Wed, 20 Feb 2013 23:24:46 +0000 Subject: [PATCH 09/26] Fix for missing value in transmitter delaction --- server/transmitter.py | 1 + testcases/servertestcase.py | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/server/transmitter.py b/server/transmitter.py index 23b609a1..3bd7fb14 100644 --- a/server/transmitter.py +++ b/server/transmitter.py @@ -183,6 +183,7 @@ class Transmitter: self.__server.addAction(name, value) return self.__server.getLastAction(name).getName() elif command[1] == "delaction": + value = command[2] self.__server.delAction(name, value) return None elif command[1] == "setcinfo": diff --git a/testcases/servertestcase.py b/testcases/servertestcase.py index 47d4ef0b..0b33a96f 100644 --- a/testcases/servertestcase.py +++ b/testcases/servertestcase.py @@ -433,9 +433,10 @@ class Transmitter(unittest.TestCase): self.__transm.proceed( ["set", self.jailName, "delcinfo", action, "KEY"]), (0, None)) - #FIXME: This is broken in server.transmitter (value not defined) - """ self.assertEqual( self.__transm.proceed(["set", self.jailName, "delaction", action]), (0, None)) - """ + self.assertEqual( + self.__transm.proceed( + ["set", self.jailName, "delaction", "Doesn't exist"]), + (0, None)) #NOTE: Should this return 1? From b36835f6f061efded70c747539a1da2d66b9d270 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks <steven@hiscocks.me.uk> Date: Wed, 20 Feb 2013 23:33:39 +0000 Subject: [PATCH 10/26] Added transmitter get cinfo option for action --- common/protocol.py | 1 + server/transmitter.py | 4 ++++ testcases/servertestcase.py | 7 ++++--- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/common/protocol.py b/common/protocol.py index 2f8ffa6c..99a2fe09 100644 --- a/common/protocol.py +++ b/common/protocol.py @@ -90,6 +90,7 @@ protocol = [ ["get <JAIL> actioncheck <ACT>", "gets the check command for the action <ACT> for <JAIL>"], ["get <JAIL> actionban <ACT>", "gets the ban command for the action <ACT> for <JAIL>"], ["get <JAIL> actionunban <ACT>", "gets the unban command for the action <ACT> for <JAIL>"], +["get <JAIL> cinfo <ACT> <KEY>", "gets the value for <KEY> for the action <ACT> for <JAIL>"], ] ## diff --git a/server/transmitter.py b/server/transmitter.py index 3bd7fb14..a02b94a2 100644 --- a/server/transmitter.py +++ b/server/transmitter.py @@ -266,6 +266,10 @@ class Transmitter: elif command[1] == "actionunban": act = command[2] return self.__server.getActionUnban(name, act) + elif command[1] == "cinfo": + act = command[2] + key = command[3] + return self.__server.getCInfo(name, act, key) raise Exception("Invalid command (no get action or not yet implemented)") def status(self, command): diff --git a/testcases/servertestcase.py b/testcases/servertestcase.py index 0b33a96f..00f56b81 100644 --- a/testcases/servertestcase.py +++ b/testcases/servertestcase.py @@ -422,13 +422,14 @@ class Transmitter(unittest.TestCase): self.__transm.proceed( ["set", self.jailName, "setcinfo", action, "KEY", "VALUE"]), (0, "VALUE")) - #TODO: Implement below in server.transmitter? - """ self.assertEqual( self.__transm.proceed( ["get", self.jailName, "cinfo", action, "KEY"]), (0, "VALUE")) - """ + self.assertEqual( + self.__transm.proceed( + ["get", self.jailName, "cinfo", action, "InvalidKey"])[0], + 1) self.assertEqual( self.__transm.proceed( ["set", self.jailName, "delcinfo", action, "KEY"]), From 012264dce169b8ae9a18a599371bcb9685ec3570 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko <debian@onerussian.com> Date: Thu, 21 Feb 2013 20:58:27 -0500 Subject: [PATCH 11/26] BF: safeguard closing of log handlers + close in reverse order otherwise there might be "stuck" handler in the queue. and closing exceptions can occur -- even stock logging guards in recent versions --- server/server.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/server/server.py b/server/server.py index 3889c491..dea303db 100644 --- a/server/server.py +++ b/server/server.py @@ -367,11 +367,20 @@ class Server: logSys.error("Unable to log to " + target) logSys.info("Logging to previous target " + self.__logTarget) return False - # Removes previous handlers - for handler in logging.getLogger("fail2ban").handlers: - # Closes the handler. + # Removes previous handlers -- in reverse order since removeHandler + # alter the list in-place and that can confuses the iterable + for handler in logging.getLogger("fail2ban").handlers[::-1]: + # Remove the handler. logging.getLogger("fail2ban").removeHandler(handler) - handler.close() + # And try to close -- it might be closed already + try: + handler.flush() + handler.close() + except ValueError: + if sys.version_info >= (2,6): + raise + # is known to be thrown after logging was shutdown once + # with older Pythons -- seems to be safe to ignore there # tell the handler to use this format hdlr.setFormatter(formatter) logging.getLogger("fail2ban").addHandler(hdlr) From 154aa38e3f8174baee6593c3022c53f25b8aa364 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko <debian@onerussian.com> Date: Thu, 21 Feb 2013 20:59:46 -0500 Subject: [PATCH 12/26] BF: do not shutdown logging until all jails stop -- so move into Server.quit() Together with previous commit it should resolve failures with the server tests on python < 2.6 --- server/server.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/server/server.py b/server/server.py index dea303db..0e1b6c97 100644 --- a/server/server.py +++ b/server/server.py @@ -97,12 +97,6 @@ class Server: except OSError, e: logSys.error("Unable to remove PID file: %s" % e) logSys.info("Exiting Fail2ban") - # Shutdowns the logging. - try: - self.__loggingLock.acquire() - logging.shutdown() - finally: - self.__loggingLock.release() def quit(self): # Stop communication first because if jail's unban action @@ -112,8 +106,17 @@ class Server: # are exiting) # See https://github.com/fail2ban/fail2ban/issues/7 self.__asyncServer.stop() + # Now stop all the jails self.stopAllJail() + + # Only now shutdown the logging. + try: + self.__loggingLock.acquire() + logging.shutdown() + finally: + self.__loggingLock.release() + def addJail(self, name, backend): self.__jails.add(name, backend) From 59c35bc44a175a672e084bc30511dfa3436ff052 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko <debian@onerussian.com> Date: Fri, 1 Mar 2013 19:57:56 -0500 Subject: [PATCH 13/26] Downgrade log rotation detection message to DEBUG level from INFO. Closes: gh-129 This message useful only when debugging problems so it is more reasonable to have it suppressed otherwise --- server/filter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/filter.py b/server/filter.py index fb79fcbe..5b2b85e0 100644 --- a/server/filter.py +++ b/server/filter.py @@ -550,7 +550,7 @@ class FileContainer: stats = os.fstat(self.__handler.fileno()) # Compare hash and inode if self.__hash != myHash or self.__ino != stats.st_ino: - logSys.info("Log rotation detected for %s" % self.__filename) + logSys.debug("Log rotation detected for %s" % self.__filename) self.__hash = myHash self.__ino = stats.st_ino self.__pos = 0 From 6e774275161712e80fa99708395958239cd1bbd6 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko <debian@onerussian.com> Date: Thu, 7 Mar 2013 13:03:49 -0500 Subject: [PATCH 14/26] refresh generated manpages (since 0.8.2 state) --- man/fail2ban-client.1 | 39 +++++++++++++++++++++++++-------------- man/fail2ban-regex.1 | 13 ++++++++----- man/fail2ban-server.1 | 13 ++++++++----- 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/man/fail2ban-client.1 b/man/fail2ban-client.1 index 1bbddf09..431e690f 100644 --- a/man/fail2ban-client.1 +++ b/man/fail2ban-client.1 @@ -1,12 +1,12 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.36. -.TH FAIL2BAN-CLIENT "1" "March 2008" "fail2ban-client v0.8.2" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.10. +.TH FAIL2BAN-CLIENT "1" "March 2013" "fail2ban-client v0.8.8" "User Commands" .SH NAME fail2ban-client \- configure and control the server .SH SYNOPSIS .B fail2ban-client [\fIOPTIONS\fR] \fI<COMMAND>\fR .SH DESCRIPTION -Fail2Ban v0.8.2 reads log file that contains password failure report +Fail2Ban v0.8.8 reads log file that contains password failure report and bans the corresponding IP addresses using firewall rules. .SH OPTIONS .TP @@ -16,6 +16,9 @@ configuration directory \fB\-s\fR <FILE> socket path .TP +\fB\-p\fR <FILE> +pidfile path +.TP \fB\-d\fR dump configuration. For debugging .TP @@ -110,7 +113,7 @@ adds <FILE> to the monitoring list of <JAIL> .TP \fBset <JAIL> dellogpath <FILE>\fR -removes <FILE> to the monitoring +removes <FILE> from the monitoring list of <JAIL> .TP \fBset <JAIL> addfailregex <REGEX>\fR @@ -140,6 +143,15 @@ back for <JAIL> sets the number of seconds <TIME> a host will be banned for <JAIL> .TP +\fBset <JAIL> usedns <VALUE>\fR +sets the usedns mode for <JAIL> +.TP +\fBset <JAIL> banip <IP>\fR +manually Ban <IP> for <JAIL> +.TP +\fBset <JAIL> unbanip <IP>\fR +manually Unban <IP> in <JAIL> +.TP \fBset <JAIL> maxretry <RETRY>\fR sets the number of failures <RETRY> before banning the host @@ -191,14 +203,6 @@ files for <JAIL> gets the list of ignored IP addresses for <JAIL> .TP -\fBget <JAIL> timeregex\fR -gets the regular expression used -for the time detection for <JAIL> -.TP -\fBget <JAIL> timepattern\fR -gets the pattern used for the time -detection for <JAIL> -.TP \fBget <JAIL> failregex\fR gets the list of regular expressions which matches the @@ -218,6 +222,9 @@ will look back for failures for gets the time a host is banned for <JAIL> .TP +\fBget <JAIL> usedns\fR +gets the usedns setting for <JAIL> +.TP \fBget <JAIL> maxretry\fR gets the number of failures allowed for <JAIL> @@ -245,15 +252,19 @@ action <ACT> for <JAIL> \fBget <JAIL> actionunban <ACT>\fR gets the unban command for the action <ACT> for <JAIL> +.TP +\fBget <JAIL> cinfo <ACT> <KEY>\fR +gets the value for <KEY> for the +action <ACT> for <JAIL> .SH FILES \fI/etc/fail2ban/*\fR .SH AUTHOR Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>. Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>. .SH "REPORTING BUGS" -Report bugs on https://github.com/fail2ban/fail2ban/issues +Report bugs to https://github.com/fail2ban/fail2ban/issues .SH COPYRIGHT -Copyright \(co 2004-2008 Cyril Jaquier +Copyright \(co 2004\-2008 Cyril Jaquier, 2008\- Fail2Ban Contributors .br Copyright of modifications held by their respective authors. Licensed under the GNU General Public License v2 (GPL). diff --git a/man/fail2ban-regex.1 b/man/fail2ban-regex.1 index 0dac089d..09b9d6b0 100644 --- a/man/fail2ban-regex.1 +++ b/man/fail2ban-regex.1 @@ -1,12 +1,12 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.36. -.TH FAIL2BAN-REGEX "1" "March 2008" "fail2ban-regex v0.8.2" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.10. +.TH FAIL2BAN-REGEX "1" "March 2013" "fail2ban-regex v0.8.8" "User Commands" .SH NAME fail2ban-regex \- test Fail2ban "failregex" option .SH SYNOPSIS .B fail2ban-regex [\fIOPTIONS\fR] \fI<LOG> <REGEX> \fR[\fIIGNOREREGEX\fR] .SH DESCRIPTION -Fail2Ban v0.8.2 reads log file that contains password failure report +Fail2Ban v0.8.8 reads log file that contains password failure report and bans the corresponding IP addresses using firewall rules. .PP This tools can test regular expressions for "fail2ban". @@ -17,6 +17,9 @@ display this help message .TP \fB\-V\fR, \fB\-\-version\fR print the version +.TP +\fB\-v\fR, \fB\-\-verbose\fR +verbose output .SH LOG .TP \fBstring\fR @@ -42,9 +45,9 @@ path to a filter file (filter.d/sshd.conf) Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>. Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>. .SH "REPORTING BUGS" -Report bugs on https://github.com/fail2ban/fail2ban/issues +Report bugs to https://github.com/fail2ban/fail2ban/issues .SH COPYRIGHT -Copyright \(co 2004-2008 Cyril Jaquier +Copyright \(co 2004\-2008 Cyril Jaquier .br Copyright of modifications held by their respective authors. Licensed under the GNU General Public License v2 (GPL). diff --git a/man/fail2ban-server.1 b/man/fail2ban-server.1 index 5f4f15b7..3f6b013f 100644 --- a/man/fail2ban-server.1 +++ b/man/fail2ban-server.1 @@ -1,12 +1,12 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.36. -.TH FAIL2BAN-SERVER "1" "March 2008" "fail2ban-server v0.8.2" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.10. +.TH FAIL2BAN-SERVER "1" "March 2013" "fail2ban-server v0.8.8" "User Commands" .SH NAME fail2ban-server \- start the server .SH SYNOPSIS .B fail2ban-server [\fIOPTIONS\fR] .SH DESCRIPTION -Fail2Ban v0.8.2 reads log file that contains password failure report +Fail2Ban v0.8.8 reads log file that contains password failure report and bans the corresponding IP addresses using firewall rules. .PP Only use this command for debugging purpose. Start the server with @@ -23,6 +23,9 @@ start in foreground \fB\-s\fR <FILE> socket path .TP +\fB\-p\fR <FILE> +pidfile path +.TP \fB\-x\fR force execution of the server (remove socket file) .TP @@ -35,9 +38,9 @@ print the version Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>. Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>. .SH "REPORTING BUGS" -Report bugs on https://github.com/fail2ban/fail2ban/issues +Report bugs to https://github.com/fail2ban/fail2ban/issues .SH COPYRIGHT -Copyright \(co 2004-2008 Cyril Jaquier +Copyright \(co 2004\-2008 Cyril Jaquier, 2008\- Fail2Ban Contributors .br Copyright of modifications held by their respective authors. Licensed under the GNU General Public License v2 (GPL). From 00ad4d56a775ed802588bf08efe5c0ec2d824a0f Mon Sep 17 00:00:00 2001 From: Daniel Black <grooverdan@users.sourceforge.net> Date: Sun, 10 Mar 2013 15:18:09 +1100 Subject: [PATCH 15/26] FSF address changes missing from previous --- README | 4 ++-- files/cacti/README | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README b/README index db97aa8b..e926509e 100644 --- a/README +++ b/README @@ -91,5 +91,5 @@ 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., 59 Temple Place, -Suite 330, Boston, MA 02111-1307 USA +Fail2Ban; if not, write to the Free Software Foundation, Inc., 51 Franklin +Street, Fifth Floor, Boston, MA 02110, USA diff --git a/files/cacti/README b/files/cacti/README index 80c97ce9..73ceb3bd 100644 --- a/files/cacti/README +++ b/files/cacti/README @@ -49,5 +49,5 @@ 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., 59 Temple Place, Suite 330, -Boston, MA 02111-1307 USA +Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110, USA From 3665e6dc445bb8108b1a3f4ed18113a7729459e3 Mon Sep 17 00:00:00 2001 From: Daniel Black <grooverdan@users.sourceforge.net> Date: Sun, 10 Mar 2013 15:18:42 +1100 Subject: [PATCH 16/26] Add development documentation and framework for code coverage measurement --- .coveragerc | 4 ++ .gitignore | 3 + DEVELOP | 122 +++++++++++++++++++++++++++++++++--- MANIFEST | 2 + fail2ban-client | 2 +- fail2ban-testcases | 22 +++---- server/asyncserver.py | 2 +- server/filter.py | 8 +-- testcases/filtertestcase.py | 6 +- 9 files changed, 144 insertions(+), 27 deletions(-) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..3bffd79a --- /dev/null +++ b/.coveragerc @@ -0,0 +1,4 @@ + +[run] +branch = True +omit = /usr* diff --git a/.gitignore b/.gitignore index 6a0d5e64..1e3b2ec4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ *~ build +dist *.pyc +htmlcov +.coverage diff --git a/DEVELOP b/DEVELOP index 7a6a7b9b..bce9e64e 100644 --- a/DEVELOP +++ b/DEVELOP @@ -24,14 +24,96 @@ Request feature. You can find more details on the Fail2Ban wiki Testing ======= -Existing tests can be run by executing `fail2ban-testcases`. +Existing tests can be run by executing `fail2ban-testcases`. This has options +like --log-level that will probably be useful. `fail2ban-testcases --help` for +full options. + +Test cases should cover all usual cases, all exception cases and all inside +/ outside boundary conditions. + +Test cases should cover all branches. The coverage tool will help identify +missing branches. Also see http://nedbatchelder.com/code/coverage/branch.html +for more details. + +Install the package python-coverage to visualise your test coverage. Run the +following: + +coverage run fail2ban-testcases +coverage html + +Then look at htmlcov/index.html and see how much coverage your test cases +exert over the codebase. Full coverage is a good thing however it may not be +complete. Try to ensure tests cover as many independant paths through the +code. + +Manual Execution. To run in a development environment do: + +./fail2ban-client -c config/ -s /tmp/f2b.sock -i start + +some quick commands: + +status +add test pyinotify +status test +set test addaction iptables +set test actionban iptables echo <ip> <cidr> >> /tmp/ban +set test actionunban iptables echo <ip> <cidr> >> /tmp/unban +get test actionban iptables +get test actionunban iptables +set test banip 192.168.2.2 +status test + -Documentation about creating tests (when tests are required and some guidelines -for creating good tests) will be added soon. Coding Standards -================ -Coming Soon. +================ + +Style +----- + +Please use tabs for now. Keep to 80 columns, at least for readable text. + +Tests +----- + +Add tests. They should test all the code you add in a meaning way. + +Coverage +-------- + +Test coverage should always increase as you add code. + +You may use "# pragma: no cover" in the code for branches of code that support +older versions on python. For all other uses of "pragma: no cover" or +"pragma: no branch" document the reason why its not covered. "I haven't written +a test case" isn't a sufficient reason. + +Documentation +------------- + +Ensure this documentation is up to date after changes. Also ensure that the man +pages still are accurage. Ensure that there is sufficient documentation for +your new features to be used. + +Bugs +---- + +Remove them and don't add any more. + +Git +--- + +Use the following tags in your commit messages: + +'ENH:' for enhancements +'BF:' for bug fixes +'DOC:' for documenation fixes + +Adding Actions +-------------- + +If you add an action.d/*.conf file also add a example in config/jail.conf +with enabled=false and maxretry=5 for ssh. Design @@ -127,12 +209,14 @@ FileContainer .__pos Keeps the position pointer + +dnsutils.py +~~~~~~~~~~~ + DNSUtils Utility class for DNS and IP handling - RF-Note: convert to functions within a separate submodule - filter*.py ~~~~~~~~~~ @@ -156,3 +240,27 @@ action.py ~~~~~~~~~ Takes care about executing start/check/ban/unban/stop commands + + +Releasing +========= + +Ensure the version is correct in ./common/version.py + +# update man pages +(cd man ; ./generate-man ) + +git commit -m 'update man pages for release' man/* + +python setup.py check +python setup.py sdist +python setup.py bdist_rpm +python setup.py upload + +Run the following and update the wiki with output: + +python -c 'import common.protocol; common.protocol.printWiki()' + +email users and development list of release + +TODO notifing distributors etc. diff --git a/MANIFEST b/MANIFEST index eef145b6..bedfe560 100644 --- a/MANIFEST +++ b/MANIFEST @@ -3,6 +3,8 @@ ChangeLog TODO THANKS COPYING +DEVELOP +doc/run-rootless.txt fail2ban-client fail2ban-server fail2ban-testcases diff --git a/fail2ban-client b/fail2ban-client index 7ee4a47c..ed211e62 100755 --- a/fail2ban-client +++ b/fail2ban-client @@ -418,7 +418,7 @@ class Fail2banClient: class ServerExecutionException(Exception): pass -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover - can't test main client = Fail2banClient() # Exit with correct return value if client.start(sys.argv): diff --git a/fail2ban-testcases b/fail2ban-testcases index d2bbfed1..ed341385 100755 --- a/fail2ban-testcases +++ b/fail2ban-testcases @@ -77,10 +77,10 @@ verbosity = {'debug': 3, 'fatal': 0, None: 1}[opts.log_level] -if opts.log_level is not None: +if opts.log_level is not None: # pragma: no cover # so we had explicit settings logSys.setLevel(getattr(logging, opts.log_level.upper())) -else: +else: # pragma: no cover # suppress the logging but it would leave unittests' progress dots # ticking, unless like with '-l fatal' which would be silent # unless error occurs @@ -89,9 +89,9 @@ else: # Add the default logging handler stdout = logging.StreamHandler(sys.stdout) # Custom log format for the verbose tests runs -if verbosity > 1: +if verbosity > 1: # pragma: no cover stdout.setFormatter(logging.Formatter(' %(asctime)-15s %(thread)s %(message)s')) -else: +else: # pragma: no cover # just prefix with the space stdout.setFormatter(logging.Formatter(' %(message)s')) logSys.addHandler(stdout) @@ -99,7 +99,7 @@ logSys.addHandler(stdout) # # Let know the version # -if not opts.log_level or opts.log_level != 'fatal': +if not opts.log_level or opts.log_level != 'fatal': # pragma: no cover print "Fail2ban %s test suite. Python %s. Please wait..." \ % (version, str(sys.version).replace('\n', '')) @@ -107,9 +107,9 @@ if not opts.log_level or opts.log_level != 'fatal': # # Gather the tests # -if not len(regexps): +if not len(regexps): # pragma: no cover tests = unittest.TestSuite() -else: +else: # pragma: no cover import re class FilteredTestSuite(unittest.TestSuite): _regexps = [re.compile(r) for r in regexps] @@ -159,13 +159,13 @@ filters = [FilterPoll] # always available try: from server.filtergamin import FilterGamin filters.append(FilterGamin) -except Exception, e: +except Exception, e: # pragma: no cover print "I: Skipping gamin backend testing. Got exception '%s'" % e try: from server.filterpyinotify import FilterPyinotify filters.append(FilterPyinotify) -except Exception, e: +except Exception, e: # pragma: no cover print "I: Skipping pyinotify backend testing. Got exception '%s'" % e for Filter_ in filters: @@ -189,7 +189,7 @@ try: tests_results = testRunner.run(tests) -finally: +finally: # pragma: no cover # Just for the sake of it reset the TZ # yoh: move all this into setup/teardown methods within tests os.environ.pop('TZ') @@ -197,5 +197,5 @@ finally: os.environ['TZ'] = old_TZ time.tzset() -if not tests_results.wasSuccessful(): +if not tests_results.wasSuccessful(): # pragma: no cover sys.exit(1) diff --git a/server/asyncserver.py b/server/asyncserver.py index 64ec8f39..66b2b53f 100644 --- a/server/asyncserver.py +++ b/server/asyncserver.py @@ -142,7 +142,7 @@ class AsyncServer(asyncore.dispatcher): if sys.version_info >= (2, 6): # if python 2.6 or greater... logSys.debug("Detected Python 2.6 or greater. asyncore.loop() not using poll") asyncore.loop(use_poll = False) # fixes the "Unexpected communication problem" issue on Python 2.6 and 3.0 - else: + else: # pragma: no cover logSys.debug("NOT Python 2.6/3.* - asyncore.loop() using poll") asyncore.loop(use_poll = True) diff --git a/server/filter.py b/server/filter.py index 5b2b85e0..fb292573 100644 --- a/server/filter.py +++ b/server/filter.py @@ -211,7 +211,7 @@ class Filter(JailThread): # file has been modified and looks for failures. # @return True when the thread exits nicely - def run(self): + def run(self): # pragma: no cover raise Exception("run() is abstract") ## @@ -226,7 +226,7 @@ class Filter(JailThread): self.failManager.addFailure(FailTicket(ip, unixTime)) # Perform the banning of the IP now. - try: + try: # pragma: no branch - exception is the only way out while True: ticket = self.failManager.toBan() self.jail.putFailTicket(ticket) @@ -373,7 +373,7 @@ class Filter(JailThread): failList.append([ip, date]) # We matched a regex, it is enough to stop. break - except RegexException, e: + except RegexException, e: # pragma: no cover - unsure if reachable logSys.error(e) return failList @@ -507,7 +507,7 @@ class FileFilter(Filter): try: import hashlib md5sum = hashlib.md5 -except ImportError: +except ImportError: # pragma: no cover # hashlib was introduced in Python 2.5. For compatibility with those # elderly Pythons, import from md5 import md5 diff --git a/testcases/filtertestcase.py b/testcases/filtertestcase.py index c10fa78d..a036f804 100644 --- a/testcases/filtertestcase.py +++ b/testcases/filtertestcase.py @@ -99,11 +99,11 @@ def _copy_lines_between_files(fin, fout, n=None, skip=0, mode='a', terminal_line Returns open fout """ - if sys.version_info[:2] <= (2,4): + if sys.version_info[:2] <= (2,4): # pragma: no cover # on old Python st_mtime is int, so we should give at least 1 sec so # polling filter could detect the change time.sleep(1) - if isinstance(fin, str): + if isinstance(fin, str): # pragma: no branch - only used with str in test cases fin = open(fin, 'r') if isinstance(fout, str): fout = open(fout, mode) @@ -353,7 +353,7 @@ def get_monitor_failures_testcase(Filter_): _killfile(self.file, self.name) pass - def __str__(self): + def __str__(self): # pragma: no cover - will only show up if unexpected exception is thrown return "MonitorFailures%s(%s)" \ % (Filter_, hasattr(self, 'name') and self.name or 'tempfile') From c8c7b0b9845f240b0e1ecaabc84def4c0a74b86e Mon Sep 17 00:00:00 2001 From: Daniel Black <grooverdan@users.sourceforge.net> Date: Sun, 10 Mar 2013 15:29:27 +1100 Subject: [PATCH 17/26] BF: general Exception catch was excessive. Only IOError and OSError are possible and has different meanings --- server/filter.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/filter.py b/server/filter.py index 5b2b85e0..ceda1377 100644 --- a/server/filter.py +++ b/server/filter.py @@ -477,10 +477,15 @@ class FileFilter(Filter): # Try to open log file. try: container.open() - except Exception, e: + # see http://python.org/dev/peps/pep-3151/ + except IOError, e: logSys.error("Unable to open %s" % filename) logSys.exception(e) return False + except OSError, e: # pragma: no cover - requires race condition to tigger this + logSys.error("Error opening %s" % filename) + logSys.exception(e) + return False while True: line = container.readline() From f0610c01d57976abba855c62d61bfaa78130bc4a Mon Sep 17 00:00:00 2001 From: Daniel Black <grooverdan@users.sourceforge.net> Date: Thu, 7 Feb 2013 21:50:28 +1100 Subject: [PATCH 18/26] BF: allow more than single word for command action[start,stop,ban,unban,check] and for setcinfo too --- server/transmitter.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/transmitter.py b/server/transmitter.py index a02b94a2..3f880c07 100644 --- a/server/transmitter.py +++ b/server/transmitter.py @@ -189,7 +189,7 @@ class Transmitter: elif command[1] == "setcinfo": act = command[2] key = command[3] - value = command[4] + value = " ".join(command[4:]) self.__server.setCInfo(name, act, key, value) return self.__server.getCInfo(name, act, key) elif command[1] == "delcinfo": @@ -199,27 +199,27 @@ class Transmitter: return None elif command[1] == "actionstart": act = command[2] - value = command[3] + value = " ".join(command[3:]) self.__server.setActionStart(name, act, value) return self.__server.getActionStart(name, act) elif command[1] == "actionstop": act = command[2] - value = command[3] + value = " ".join(command[3:]) self.__server.setActionStop(name, act, value) return self.__server.getActionStop(name, act) elif command[1] == "actioncheck": act = command[2] - value = command[3] + value = " ".join(command[3:]) self.__server.setActionCheck(name, act, value) return self.__server.getActionCheck(name, act) elif command[1] == "actionban": act = command[2] - value = command[3] + value = " ".join(command[3:]) self.__server.setActionBan(name, act, value) return self.__server.getActionBan(name, act) elif command[1] == "actionunban": act = command[2] - value = command[3] + value = " ".join(command[3:]) self.__server.setActionUnban(name, act, value) return self.__server.getActionUnban(name, act) raise Exception("Invalid command (no set action or not yet implemented)") From 7cd6dab7f01869a3647cc5fe5a30a6d3af0b19da Mon Sep 17 00:00:00 2001 From: Daniel Black <grooverdan@users.sourceforge.net> Date: Thu, 7 Feb 2013 20:25:59 +1100 Subject: [PATCH 19/26] ENH: add help command --- common/protocol.py | 1 + fail2ban-client | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/common/protocol.py b/common/protocol.py index 99a2fe09..1083a94b 100644 --- a/common/protocol.py +++ b/common/protocol.py @@ -40,6 +40,7 @@ protocol = [ ["stop", "stops all jails and terminate the server"], ["status", "gets the current status of the server"], ["ping", "tests if the server is alive"], +["help", "return this output"], ['', "LOGGING", ""], ["set loglevel <LEVEL>", "sets logging level to <LEVEL>. 0 is minimal, 4 is debug"], ["get loglevel", "gets the logging level"], diff --git a/fail2ban-client b/fail2ban-client index 7ee4a47c..76c18c04 100755 --- a/fail2ban-client +++ b/fail2ban-client @@ -380,7 +380,9 @@ class Fail2banClient: if cmd == "exit" or cmd == "quit": # Exit return True - if not cmd == "": + if cmd == "help": + self.dispUsage() + elif not cmd == "": self.__processCommand(shlex.split(cmd)) except (EOFError, KeyboardInterrupt): print From a0f088be256a0f0c84a44c309d71779a5ca11e19 Mon Sep 17 00:00:00 2001 From: Daniel Black <grooverdan@users.sourceforge.net> Date: Sun, 10 Mar 2013 16:28:45 +1100 Subject: [PATCH 20/26] ENH: typo + head -1 has been deprecated for 10+ years. --- config/action.d/dshield.conf | 4 ++-- config/action.d/mynetwatchman.conf | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/action.d/dshield.conf b/config/action.d/dshield.conf index 177329b2..c0d87987 100644 --- a/config/action.d/dshield.conf +++ b/config/action.d/dshield.conf @@ -124,13 +124,13 @@ port = ??? userid = 0 # Option: myip -# Notes.: TThe target IP for the attack (your public IP). Should be provided +# Notes.: The target IP for the attack (your public IP). Should be provided # either in the jail config or in a .local file unless your PUBLIC IP # is the first IP assigned to eth0 # Values: [ an IP address ] Default: Tries to find the IP address of eth0, # which in most cases will be a private IP, and therefore incorrect # -myip = `ip -4 addr show dev eth0 | grep inet | head -1 | sed -r 's/.*inet ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}).*/\1/'` +myip = `ip -4 addr show dev eth0 | grep inet | head -n 1 | sed -r 's/.*inet ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}).*/\1/'` # Option: protocol # Notes.: The protocol over which the attack is happening diff --git a/config/action.d/mynetwatchman.conf b/config/action.d/mynetwatchman.conf index d4f8de1a..3ae3e6db 100644 --- a/config/action.d/mynetwatchman.conf +++ b/config/action.d/mynetwatchman.conf @@ -102,13 +102,13 @@ mnwlogin = mnwpass = # Option: myip -# Notes.: TThe target IP for the attack (your public IP). Should be overridden +# Notes.: The target IP for the attack (your public IP). Should be overridden # either in the jail config or in a .local file unless your PUBLIC IP # is the first IP assigned to eth0 # Values: [ an IP address ] Default: Tries to find the IP address of eth0, # which in most cases will be a private IP, and therefore incorrect # -myip = `ip -4 addr show dev eth0 | grep inet | head -1 | sed -r 's/.*inet ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}).*/\1/'` +myip = `ip -4 addr show dev eth0 | grep inet | head -n 1 | sed -r 's/.*inet ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}).*/\1/'` # Option: protocol # Notes.: The protocol over which the attack is happening From 23bbc60b1ccebb71df499aadbab2e19f8c38737f Mon Sep 17 00:00:00 2001 From: Daniel Black <grooverdan@users.sourceforge.net> Date: Sun, 10 Mar 2013 17:10:40 +1100 Subject: [PATCH 21/26] do catch all exception --- server/filter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/filter.py b/server/filter.py index ceda1377..35da1953 100644 --- a/server/filter.py +++ b/server/filter.py @@ -486,6 +486,10 @@ class FileFilter(Filter): logSys.error("Error opening %s" % filename) logSys.exception(e) return False + except OSError, e: # pragma: no cover - Requires implemention error in FileContainer to generate + logSys.error("Internal errror in FileContainer open method - please report as a bug to https://github.com/fail2ban/fail2ban/issues") + logSys.exception(e) + return False while True: line = container.readline() From 8b91b58e2dc1fe59eebc41937d7cffaa2e26f2f3 Mon Sep 17 00:00:00 2001 From: Daniel Black <grooverdan@users.sourceforge.net> Date: Sun, 10 Mar 2013 17:33:16 +1100 Subject: [PATCH 22/26] add corresponding ChangeLog entry --- DEVELOP | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DEVELOP b/DEVELOP index bce9e64e..25369830 100644 --- a/DEVELOP +++ b/DEVELOP @@ -247,6 +247,8 @@ Releasing Ensure the version is correct in ./common/version.py +Add/finalize the corresponding entry in the ChangeLog + # update man pages (cd man ; ./generate-man ) From d0d89b43217ff889269b7c1f87a6b3af147e4993 Mon Sep 17 00:00:00 2001 From: Daniel Black <grooverdan@users.sourceforge.net> Date: Sun, 10 Mar 2013 17:33:32 +1100 Subject: [PATCH 23/26] TODO: test filters/examples files --- TODO | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TODO b/TODO index fc43dc2c..933134e9 100644 --- a/TODO +++ b/TODO @@ -13,6 +13,9 @@ Legend: # partially done * done +- Run tests though all filters/examples files - (see sshd example file) as unit + test + - Removed relative imports - Cleanup fail2ban-client and fail2ban-server. Move code to server/ and client/ From 4bbbc0787225a54d556924c1126419c2b0a8f35c Mon Sep 17 00:00:00 2001 From: Steven Hiscocks <steven@hiscocks.me.uk> Date: Sun, 10 Mar 2013 14:55:39 +0000 Subject: [PATCH 24/26] Added additional Transmitter tests, and some associated fixes This includes some tweaks such that errors are raised for certain commands --- server/actions.py | 7 ++-- server/filter.py | 2 ++ server/transmitter.py | 10 ++++-- testcases/servertestcase.py | 66 ++++++++++++++++++++++++++++++------- 4 files changed, 67 insertions(+), 18 deletions(-) diff --git a/server/actions.py b/server/actions.py index 4d021a71..ddcc83d6 100644 --- a/server/actions.py +++ b/server/actions.py @@ -77,7 +77,8 @@ class Actions(JailThread): for action in self.__actions: if action.getName() == name: self.__actions.remove(action) - break + return + raise KeyError("Invalid Action name: %s" % name) ## # Returns an action. @@ -91,7 +92,7 @@ class Actions(JailThread): for action in self.__actions: if action.getName() == name: return action - raise KeyError + raise KeyError("Invalid Action name") ## # Returns the last defined action. @@ -131,7 +132,7 @@ class Actions(JailThread): # Unban the IP. self.__unBan(ticket) return ip - return 'None' + raise ValueError("IP %s is not banned" % ip) ## # Main loop. diff --git a/server/filter.py b/server/filter.py index fb292573..dc82d7aa 100644 --- a/server/filter.py +++ b/server/filter.py @@ -93,6 +93,7 @@ class Filter(JailThread): self.__failRegex.append(regex) except RegexException, e: logSys.error(e) + raise e def delFailRegex(self, index): @@ -126,6 +127,7 @@ class Filter(JailThread): self.__ignoreRegex.append(regex) except RegexException, e: logSys.error(e) + raise e def delIgnoreRegex(self, index): try: diff --git a/server/transmitter.py b/server/transmitter.py index a02b94a2..c9033010 100644 --- a/server/transmitter.py +++ b/server/transmitter.py @@ -112,14 +112,18 @@ class Transmitter: return self.__server.getLogLevel() elif name == "logtarget": value = command[1] - self.__server.setLogTarget(value) - return self.__server.getLogTarget() + if self.__server.setLogTarget(value): + return self.__server.getLogTarget() + else: + raise Exception("Failed to change log target") # Jail elif command[1] == "idle": if command[2] == "on": self.__server.setIdleJail(name, True) elif command[2] == "off": self.__server.setIdleJail(name, False) + else: + raise Exception("Invalid idle option, must be 'yes' or 'no'") return self.__server.getIdleJail(name) # Filter elif command[1] == "addignoreip": @@ -275,7 +279,7 @@ class Transmitter: def status(self, command): if len(command) == 0: return self.__server.status() - else: + elif len(command) == 1: name = command[0] return self.__server.statusJail(name) raise Exception("Invalid command (no status)") diff --git a/testcases/servertestcase.py b/testcases/servertestcase.py index 00f56b81..4268df38 100644 --- a/testcases/servertestcase.py +++ b/testcases/servertestcase.py @@ -29,6 +29,7 @@ __license__ = "GPL" import unittest, socket, time, tempfile, os from server.server import Server +from common.exceptions import UnknownJailException class StartStop(unittest.TestCase): @@ -141,6 +142,9 @@ class Transmitter(unittest.TestCase): self.__transm.proceed(["get", jail, cmd]), (0, outValues[n+1:])) + def testStopServer(self): + self.assertEqual(self.__transm.proceed(["stop"]), (0, None)) + def testPing(self): self.assertEqual(self.__transm.proceed(["ping"]), (0, "pong")) @@ -162,11 +166,7 @@ class Transmitter(unittest.TestCase): # If path is invalid, do not change logtarget value = "/this/path/should/not/exist" - self.assertEqual( - self.__transm.proceed(["set", "logtarget", value]), - (0, logTarget)) #NOTE: Shouldn't this return 1 - self.assertEqual( - self.__transm.proceed(["get", "logtarget"]), (0, logTargets[-1])) + self.setGetTestNOK("logtarget", value) self.__transm.proceed(["set", "/dev/null"]) for logTarget in logTargets: @@ -196,6 +196,28 @@ class Transmitter(unittest.TestCase): self.assertEqual( self.__transm.proceed(["add", "all", "polling"])[0], 1) + def testStartStopJail(self): + self.assertEqual( + self.__transm.proceed(["start", self.jailName]), (0, None)) + time.sleep(1) + self.assertEqual( + self.__transm.proceed(["stop", self.jailName]), (0, None)) + self.assertRaises( + UnknownJailException, self.__server.isAlive, self.jailName) + + def testStartStopAllJail(self): + self.__server.addJail("TestJail2", "auto") + self.assertEqual( + self.__transm.proceed(["start", self.jailName]), (0, None)) + self.assertEqual( + self.__transm.proceed(["start", "TestJail2"]), (0, None)) + self.assertEqual(self.__transm.proceed(["stop", "all"]), (0, None)) + time.sleep(1) + self.assertRaises( + UnknownJailException, self.__server.isAlive, self.jailName) + self.assertRaises( + UnknownJailException, self.__server.isAlive, "TestJail2") + def testJailIdle(self): self.assertEqual( self.__transm.proceed(["set", self.jailName, "idle", "on"]), @@ -205,7 +227,7 @@ class Transmitter(unittest.TestCase): (0, False)) self.assertEqual( self.__transm.proceed(["set", self.jailName, "idle", "CAT"])[0], - 0) #NOTE: Should this return 1 + 1) def testJailFindTime(self): self.setGetTest("findtime", "120", 120, jail=self.jailName) @@ -249,8 +271,7 @@ class Transmitter(unittest.TestCase): # Unban IP which isn't banned self.assertEqual( self.__transm.proceed( - ["set", self.jailName, "unbanip", "192.168.1.1"]), - (0, "None")) #NOTE: Should this return 1? + ["set", self.jailName, "unbanip", "192.168.1.1"])[0],1) def testJailMaxRetry(self): self.setGetTest("maxretry", "5", 5, jail=self.jailName) @@ -335,8 +356,8 @@ class Transmitter(unittest.TestCase): self.assertEqual( self.__transm.proceed( - ["set", self.jailName, "addfailregex", "No host regex"]), - (0, [])) #NOTE: Shouldn't this return 1? + ["set", self.jailName, "addfailregex", "No host regex"])[0], + 1) self.assertEqual( self.__transm.proceed( ["set", self.jailName, "addfailregex", 654])[0], @@ -357,6 +378,10 @@ class Transmitter(unittest.TestCase): self.jailName ) + self.assertEqual( + self.__transm.proceed( + ["set", self.jailName, "addignoreregex", "Invalid [regex"])[0], + 1) self.assertEqual( self.__transm.proceed( ["set", self.jailName, "addignoreregex", 50])[0], @@ -409,6 +434,9 @@ class Transmitter(unittest.TestCase): self.assertEqual( self.__transm.proceed(["set", self.jailName, "addaction", action]), (0, action)) + self.assertEqual( + self.__transm.proceed(["get", self.jailName, "addaction", action]), + (0, action)) for cmd, value in zip(cmdList, cmdValueList): self.assertEqual( self.__transm.proceed( @@ -439,5 +467,19 @@ class Transmitter(unittest.TestCase): (0, None)) self.assertEqual( self.__transm.proceed( - ["set", self.jailName, "delaction", "Doesn't exist"]), - (0, None)) #NOTE: Should this return 1? + ["set", self.jailName, "delaction", "Doesn't exist"])[0],1) + + def testNOK(self): + self.assertEqual(self.__transm.proceed(["INVALID", "COMMAND"])[0],1) + + def testSetNOK(self): + self.assertEqual( + self.__transm.proceed(["set", "INVALID", "COMMAND"])[0],1) + + def testGetNOK(self): + self.assertEqual( + self.__transm.proceed(["get", "INVALID", "COMMAND"])[0],1) + + def testStatusNOK(self): + self.assertEqual( + self.__transm.proceed(["status", "INVALID", "COMMAND"])[0],1) From a2b29b4875455662b48b2f9f3537bc17445a887d Mon Sep 17 00:00:00 2001 From: Pascal Borreli <pascal@borreli.com> Date: Sun, 10 Mar 2013 22:05:33 +0000 Subject: [PATCH 25/26] Fixed typos --- DEVELOP | 6 +++--- config/action.d/dshield.conf | 2 +- config/action.d/iptables-allports.conf | 2 +- config/action.d/iptables-ipset-proto4.conf | 2 +- config/action.d/iptables-ipset-proto6.conf | 2 +- config/action.d/iptables-multiport-log.conf | 2 +- config/action.d/iptables-multiport.conf | 2 +- config/action.d/iptables-new.conf | 2 +- config/action.d/iptables-xt_recent-echo.conf | 4 ++-- config/action.d/iptables.conf | 2 +- config/action.d/mail-whois-lines.conf | 2 +- config/action.d/mail-whois.conf | 2 +- config/action.d/mail.conf | 2 +- config/action.d/sendmail-buffered.conf | 2 +- config/action.d/sendmail-whois-lines.conf | 2 +- config/action.d/sendmail-whois.conf | 2 +- config/action.d/sendmail.conf | 2 +- config/filter.d/apache-badbots.conf | 2 +- config/jail.conf | 2 +- doc/run-rootless.txt | 14 +++++++------- fail2ban-client | 2 +- server/action.py | 4 ++-- server/filter.py | 8 ++++---- server/filterpoll.py | 2 +- 24 files changed, 37 insertions(+), 37 deletions(-) diff --git a/DEVELOP b/DEVELOP index 25369830..cddf4456 100644 --- a/DEVELOP +++ b/DEVELOP @@ -92,7 +92,7 @@ Documentation ------------- Ensure this documentation is up to date after changes. Also ensure that the man -pages still are accurage. Ensure that there is sufficient documentation for +pages still are accurate. Ensure that there is sufficient documentation for your new features to be used. Bugs @@ -107,7 +107,7 @@ Use the following tags in your commit messages: 'ENH:' for enhancements 'BF:' for bug fixes -'DOC:' for documenation fixes +'DOC:' for documentation fixes Adding Actions -------------- @@ -265,4 +265,4 @@ python -c 'import common.protocol; common.protocol.printWiki()' email users and development list of release -TODO notifing distributors etc. +TODO notifying distributors etc. diff --git a/config/action.d/dshield.conf b/config/action.d/dshield.conf index 177329b2..fea8f196 100644 --- a/config/action.d/dshield.conf +++ b/config/action.d/dshield.conf @@ -116,7 +116,7 @@ actionunban = if [ -f <tmpfile>.first ]; then port = ??? # Option: userid -# Notes.: Your DSheild user ID. Should be provided either in the jail config or +# Notes.: Your DShield user ID. Should be provided either in the jail config or # in a .local file. # Register at https://secure.dshield.org/register.html # Values: [ NUM ] Default: 0 diff --git a/config/action.d/iptables-allports.conf b/config/action.d/iptables-allports.conf index f534fee9..c7f76c83 100644 --- a/config/action.d/iptables-allports.conf +++ b/config/action.d/iptables-allports.conf @@ -53,7 +53,7 @@ actionunban = iptables -D fail2ban-<name> -s <ip> -j DROP [Init] -# Defaut name of the chain +# Default name of the chain # name = default diff --git a/config/action.d/iptables-ipset-proto4.conf b/config/action.d/iptables-ipset-proto4.conf index f7d03f67..665d35cf 100644 --- a/config/action.d/iptables-ipset-proto4.conf +++ b/config/action.d/iptables-ipset-proto4.conf @@ -53,7 +53,7 @@ actionunban = ipset --test fail2ban-<name> <ip> && ipset --del fail2ban-<name> < [Init] -# Defaut name of the ipset +# Default name of the ipset # name = default diff --git a/config/action.d/iptables-ipset-proto6.conf b/config/action.d/iptables-ipset-proto6.conf index 3352d63d..e4ceb0d0 100644 --- a/config/action.d/iptables-ipset-proto6.conf +++ b/config/action.d/iptables-ipset-proto6.conf @@ -53,7 +53,7 @@ actionunban = ipset del fail2ban-<name> <ip> -exist [Init] -# Defaut name of the ipset +# Default name of the ipset # name = default diff --git a/config/action.d/iptables-multiport-log.conf b/config/action.d/iptables-multiport-log.conf index 9cdc4bab..0f4947dd 100644 --- a/config/action.d/iptables-multiport-log.conf +++ b/config/action.d/iptables-multiport-log.conf @@ -61,7 +61,7 @@ actionunban = iptables -D fail2ban-<name> -s <ip> -j fail2ban-<name>-log [Init] -# Defaut name of the chain +# Default name of the chain # name = default diff --git a/config/action.d/iptables-multiport.conf b/config/action.d/iptables-multiport.conf index d062f74d..8b15ad86 100644 --- a/config/action.d/iptables-multiport.conf +++ b/config/action.d/iptables-multiport.conf @@ -51,7 +51,7 @@ actionunban = iptables -D fail2ban-<name> -s <ip> -j DROP [Init] -# Defaut name of the chain +# Default name of the chain # name = default diff --git a/config/action.d/iptables-new.conf b/config/action.d/iptables-new.conf index 92bd9a55..64f54582 100644 --- a/config/action.d/iptables-new.conf +++ b/config/action.d/iptables-new.conf @@ -53,7 +53,7 @@ actionunban = iptables -D fail2ban-<name> -s <ip> -j DROP [Init] -# Defaut name of the chain +# Default name of the chain # name = default diff --git a/config/action.d/iptables-xt_recent-echo.conf b/config/action.d/iptables-xt_recent-echo.conf index f3ac80b5..170fef07 100644 --- a/config/action.d/iptables-xt_recent-echo.conf +++ b/config/action.d/iptables-xt_recent-echo.conf @@ -11,7 +11,7 @@ # Notes.: command executed once at the start of Fail2Ban. # Values: CMD # -# Changing iptables rules requires root priviledges. If fail2ban is +# Changing iptables rules requires root privileges. If fail2ban is # configured to run as root, firewall setup can be performed by # fail2ban automatically. However, if fail2ban is configured to run as # a normal user, the configuration must be done by some other means @@ -65,7 +65,7 @@ actionunban = echo -<ip> > /proc/net/xt_recent/fail2ban-<name> [Init] -# Defaut name of the chain +# Default name of the chain # name = default diff --git a/config/action.d/iptables.conf b/config/action.d/iptables.conf index a846f971..083065fc 100644 --- a/config/action.d/iptables.conf +++ b/config/action.d/iptables.conf @@ -51,7 +51,7 @@ actionunban = iptables -D fail2ban-<name> -s <ip> -j DROP [Init] -# Defaut name of the chain +# Default name of the chain # name = default diff --git a/config/action.d/mail-whois-lines.conf b/config/action.d/mail-whois-lines.conf index 6d3343b6..1ace68f6 100644 --- a/config/action.d/mail-whois-lines.conf +++ b/config/action.d/mail-whois-lines.conf @@ -62,7 +62,7 @@ actionunban = [Init] -# Defaut name of the chain +# Default name of the chain # name = default diff --git a/config/action.d/mail-whois.conf b/config/action.d/mail-whois.conf index 9e6ec88e..1581c42c 100644 --- a/config/action.d/mail-whois.conf +++ b/config/action.d/mail-whois.conf @@ -59,7 +59,7 @@ actionunban = [Init] -# Defaut name of the chain +# Default name of the chain # name = default diff --git a/config/action.d/mail.conf b/config/action.d/mail.conf index 01f22ff8..02946b43 100644 --- a/config/action.d/mail.conf +++ b/config/action.d/mail.conf @@ -57,7 +57,7 @@ actionunban = [Init] -# Defaut name of the chain +# Default name of the chain # name = default diff --git a/config/action.d/sendmail-buffered.conf b/config/action.d/sendmail-buffered.conf index 25a23b78..80f71374 100644 --- a/config/action.d/sendmail-buffered.conf +++ b/config/action.d/sendmail-buffered.conf @@ -83,7 +83,7 @@ actionunban = [Init] -# Defaut name of the chain +# Default name of the chain # name = default diff --git a/config/action.d/sendmail-whois-lines.conf b/config/action.d/sendmail-whois-lines.conf index b84440d0..dfbf9ff8 100644 --- a/config/action.d/sendmail-whois-lines.conf +++ b/config/action.d/sendmail-whois-lines.conf @@ -73,7 +73,7 @@ actionunban = [Init] -# Defaut name of the chain +# Default name of the chain # name = default diff --git a/config/action.d/sendmail-whois.conf b/config/action.d/sendmail-whois.conf index 363c3398..e6a15fb5 100644 --- a/config/action.d/sendmail-whois.conf +++ b/config/action.d/sendmail-whois.conf @@ -71,7 +71,7 @@ actionunban = [Init] -# Defaut name of the chain +# Default name of the chain # name = default diff --git a/config/action.d/sendmail.conf b/config/action.d/sendmail.conf index bdc2a3b6..5bb75575 100644 --- a/config/action.d/sendmail.conf +++ b/config/action.d/sendmail.conf @@ -69,7 +69,7 @@ actionunban = [Init] -# Defaut name of the chain +# Default name of the chain # name = default diff --git a/config/filter.d/apache-badbots.conf b/config/filter.d/apache-badbots.conf index 1d70bb0e..1c60676d 100644 --- a/config/filter.d/apache-badbots.conf +++ b/config/filter.d/apache-badbots.conf @@ -16,7 +16,7 @@ badbots = atSpider/1\.0|autoemailspider|China Local Browse 2\.6|ContentSmartz|Da # Option: failregex # Notes.: Regexp to catch known spambots and software alike. Please verify # that it is your intent to block IPs which were driven by -# abovementioned bots. +# above mentioned bots. # Values: TEXT # failregex = ^<HOST> -.*"(GET|POST).*HTTP.*"(?:%(badbots)s|%(badbotscustom)s)"$ diff --git a/config/jail.conf b/config/jail.conf index a0093f68..8bb1a6b6 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -241,7 +241,7 @@ logpath = /var/log/lighttpd/error.log maxretry = 2 # Same as above for mod_auth -# It catches wrong authentifications +# It catches wrong authentications [lighttpd-auth] diff --git a/doc/run-rootless.txt b/doc/run-rootless.txt index 85a8f766..5edf6ef6 100644 --- a/doc/run-rootless.txt +++ b/doc/run-rootless.txt @@ -1,7 +1,7 @@ -Fail2ban normally requires root priviledges to insert iptables rules +Fail2ban normally requires root privileges to insert iptables rules through calls to /sbin/iptables and also to read the logfiles. -Fail2ban can run as an unpriviledged user provided that those two -capabilites are preserved. The idea is to run fail2ban as a normal +Fail2ban can run as an unprivileged user provided that those two +capabilities are preserved. The idea is to run fail2ban as a normal user (e.g. fail2ban) who belongs to a group which is allowed to read logfiles. The user should also be allowed to write to /proc/net/xt_recent/fail2ban-<name> (name is specified in the iptables @@ -20,14 +20,14 @@ Another way to use xt_recent is by inserting the rules by writing to action. Files in /proc/net/xt_recent/ are protected by normal filesystem rules, so can be chown'ed and chmod'ed to be writable by a certain user. After the necessary iptables rules are inserted (which -requires root priviledges), blacklisting can be perfomed by an -unpriviledged user. +requires root privileges), blacklisting can be performed by an +unprivileged user. Using fail2ban with xt_recent allows smarter filtering than normal iptables rules with the xt_recent module can provide. The disadvantage is that fail2ban cannot perform the setup by itself, -which would require the priviledge to call /sbin/iptables, and it must +which would require the privilege to call /sbin/iptables, and it must be done through other means. The primary advantage is obvious: it's generally better to run @@ -46,7 +46,7 @@ some user and thus allow delisting IPs by helper administrators without the ability to mess up other iptables rules. The xt_recent-echo jail can be used under the root user without -further configuration. To run not as root, futher setup is necessary: +further configuration. To run not as root, further setup is necessary: - Create user: diff --git a/fail2ban-client b/fail2ban-client index ed211e62..b832450e 100755 --- a/fail2ban-client +++ b/fail2ban-client @@ -297,7 +297,7 @@ class Fail2banClient: delta = -1 elif pos < 2: delta = 1 - # The server has 30 secondes to start. + # The server has 30 seconds to start. if cnt >= 300: if self.__conf["verbose"] > 1: sys.stdout.write('\n') diff --git a/server/action.py b/server/action.py index 35974c70..5fde3ae1 100644 --- a/server/action.py +++ b/server/action.py @@ -277,8 +277,8 @@ class Action: # Executes a command with preliminary checks and substitutions. # # Before executing any commands, executes the "check" command first - # in order to check if prerequirements are met. If this check fails, - # it tries to restore a sane environnement before executing the real + # in order to check if pre-requirements are met. If this check fails, + # it tries to restore a sane environment before executing the real # command. # Replaces "aInfo" and "cInfo" in the query too. # diff --git a/server/filter.py b/server/filter.py index fb292573..64a6c5e5 100644 --- a/server/filter.py +++ b/server/filter.py @@ -44,7 +44,7 @@ logSys = logging.getLogger("fail2ban.filter") # Log reader class. # # This class reads a log file and detects login failures or anything else -# that matches a given regular expression. This class is instanciated by +# that matches a given regular expression. This class is instantiated by # a Jail object. class Filter(JailThread): @@ -117,7 +117,7 @@ class Filter(JailThread): # Add the regular expression which matches the failure. # # The regular expression can also match any other pattern than failures - # and thus can be used for many purporse. + # and thus can be used for many purpose. # @param value the regular expression def addIgnoreRegex(self, value): @@ -414,7 +414,7 @@ class FileFilter(Filter): def _addLogPath(self, path): # nothing to do by default - # to be overriden by backends + # to be overridden by backends pass @@ -433,7 +433,7 @@ class FileFilter(Filter): def _delLogPath(self, path): # nothing to do by default - # to be overriden by backends + # to be overridden by backends pass ## diff --git a/server/filterpoll.py b/server/filterpoll.py index 0719545a..f0e23ac1 100644 --- a/server/filterpoll.py +++ b/server/filterpoll.py @@ -39,7 +39,7 @@ logSys = logging.getLogger("fail2ban.filter") # Log reader class. # # This class reads a log file and detects login failures or anything else -# that matches a given regular expression. This class is instanciated by +# that matches a given regular expression. This class is instantiated by # a Jail object. class FilterPoll(FileFilter): From cabbc0fd96c71e34c65b64febdac019681055c7a Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko <debian@onerussian.com> Date: Sun, 10 Mar 2013 21:21:27 -0400 Subject: [PATCH 26/26] DOC: added a note that coverage script is python-coverage on Debian systems --- DEVELOP | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DEVELOP b/DEVELOP index cddf4456..09bf7277 100644 --- a/DEVELOP +++ b/DEVELOP @@ -36,7 +36,8 @@ missing branches. Also see http://nedbatchelder.com/code/coverage/branch.html for more details. Install the package python-coverage to visualise your test coverage. Run the -following: +following (note: on Debian-based systems, the script is called +`python-coverage`): coverage run fail2ban-testcases coverage html