diff --git a/fail2ban/client/beautifier.py b/fail2ban/client/beautifier.py index 97cd38b2..4c913a9a 100644 --- a/fail2ban/client/beautifier.py +++ b/fail2ban/client/beautifier.py @@ -71,23 +71,37 @@ class Beautifier: elif inC[0] == "echo": msg = ' '.join(msg) elif inC[0:1] == ['status']: - if len(inC) > 1: - # Display information - msg = ["Status for the jail: %s" % inC[1]] + def jail_stat(response, pref=""): + # Display jail information for n, res1 in enumerate(response): - prefix1 = "`-" if n == len(response) - 1 else "|-" + prefix1 = pref + ("`-" if n == len(response) - 1 else "|-") msg.append("%s %s" % (prefix1, res1[0])) - prefix1 = " " if n == len(response) - 1 else "| " + prefix1 = pref + (" " if n == len(response) - 1 else "| ") for m, res2 in enumerate(res1[1]): prefix2 = prefix1 + ("`-" if m == len(res1[1]) - 1 else "|-") val = " ".join(map(str, res2[1])) if isinstance(res2[1], list) else res2[1] msg.append("%s %s:\t%s" % (prefix2, res2[0], val)) + if len(inC) > 1 and inC[1] != "--all": + msg = ["Status for the jail: %s" % inC[1]] + jail_stat(response) else: + jstat = None + if len(inC) > 1: # --all + jstat = response[-1] + response = response[:-1] msg = ["Status"] for n, res1 in enumerate(response): - prefix1 = "`-" if n == len(response) - 1 else "|-" + prefix1 = "`-" if not jstat and n == len(response) - 1 else "|-" val = " ".join(map(str, res1[1])) if isinstance(res1[1], list) else res1[1] msg.append("%s %s:\t%s" % (prefix1, res1[0], val)) + if jstat: + msg.append("`- Status for the jails:") + i = 0 + for n, j in jstat.items(): + i += 1 + prefix1 = "`-" if i == len(jstat) else "|-" + msg.append(" %s Jail: %s" % (prefix1, n)) + jail_stat(j, " " if i == len(jstat) else " | ") msg = "\n".join(msg) elif len(inC) < 2: pass # to few cmd args for below diff --git a/fail2ban/protocol.py b/fail2ban/protocol.py index a81c6657..5764737b 100644 --- a/fail2ban/protocol.py +++ b/fail2ban/protocol.py @@ -58,6 +58,7 @@ protocol = [ ["banned", "return jails with banned IPs as dictionary"], ["banned ... ]", "return list(s) of jails where given IP(s) are banned"], ["status", "gets the current status of the server"], +["status --all [FLAVOR]", "gets the current status of all jails, with optional flavor or extended info"], ["ping", "tests if the server is alive"], ["echo", "for internal usage, returns back and outputs a given string"], ["help", "return this output"], diff --git a/fail2ban/server/server.py b/fail2ban/server/server.py index 7c6fc2f7..f712c751 100644 --- a/fail2ban/server/server.py +++ b/fail2ban/server/server.py @@ -604,14 +604,18 @@ class Server: return 1 # Status - def status(self): + def status(self, name="", flavor="basic"): try: self.__lock.acquire() - jails = list(self.__jails) - jails.sort() - jailList = ", ".join(jails) - ret = [("Number of jail", len(self.__jails)), - ("Jail list", jailList)] + jails = sorted(self.__jails.items()) + jailList = [n for n, j in jails] + ret = [("Number of jail", len(jailList)), + ("Jail list", ", ".join(jailList))] + if name == '--all': + jstat = dict(jails) + for n, j in jails: + jstat[n] = j.status(flavor=flavor) + ret.append(jstat) return ret finally: self.__lock.release() diff --git a/fail2ban/server/transmitter.py b/fail2ban/server/transmitter.py index 28931344..da2f3072 100644 --- a/fail2ban/server/transmitter.py +++ b/fail2ban/server/transmitter.py @@ -512,11 +512,10 @@ class Transmitter: def status(self, command): if len(command) == 0: return self.__server.status() - elif len(command) == 1: + elif len(command) >= 1 and len(command) <= 2: name = command[0] - return self.__server.statusJail(name) - elif len(command) == 2: - name = command[0] - flavor = command[1] + flavor = command[1] if len(command) == 2 else "basic" + if name == "--all": + return self.__server.status("--all", flavor) return self.__server.statusJail(name, flavor=flavor) raise Exception("Invalid command (no status)") diff --git a/fail2ban/tests/clientbeautifiertestcase.py b/fail2ban/tests/clientbeautifiertestcase.py index 79a0ff54..bd18dab4 100644 --- a/fail2ban/tests/clientbeautifiertestcase.py +++ b/fail2ban/tests/clientbeautifiertestcase.py @@ -70,8 +70,8 @@ class BeautifierTest(unittest.TestCase): def testStatus(self): self.b.setInputCmd(["status"]) - response = (("Number of jails", 0), ("Jail list", ["ssh", "exim4"])) - output = "Status\n|- Number of jails:\t0\n`- Jail list:\tssh exim4" + response = (("Number of jails", 2), ("Jail list", ", ".join(["ssh", "exim4"]))) + output = "Status\n|- Number of jails:\t2\n`- Jail list:\tssh, exim4" self.assertEqual(self.b.beautify(response), output) self.b.setInputCmd(["status", "ssh"]) @@ -105,6 +105,69 @@ class BeautifierTest(unittest.TestCase): output += " `- Banned IP list: 192.168.0.1 10.2.2.1 2001:db8::1" self.assertEqual(self.b.beautify(response), output) + self.b.setInputCmd(["status", "--all"]) + response = (("Number of jails", 2), ("Jail list", ", ".join(["ssh", "exim4"])), { + "ssh": ( + ("Filter", [ + ("Currently failed", 0), + ("Total failed", 0), + ("File list", "/var/log/auth.log") + ] + ), + ("Actions", [ + ("Currently banned", 3), + ("Total banned", 3), + ("Banned IP list", [ + IPAddr("192.168.0.1"), + IPAddr("::ffff:10.2.2.1"), + IPAddr("2001:db8::1") + ] + ) + ] + ) + ), + "exim4": ( + ("Filter", [ + ("Currently failed", 3), + ("Total failed", 6), + ("File list", "/var/log/exim4/mainlog") + ] + ), + ("Actions", [ + ("Currently banned", 0), + ("Total banned", 0), + ("Banned IP list", [] + ) + ] + ) + ) + }) + output = ( + "Status\n" + + "|- Number of jails:\t2\n" + + "|- Jail list:\tssh, exim4\n" + + "`- Status for the jails:\n" + + " |- Jail: ssh\n" + + " | |- Filter\n" + + " | | |- Currently failed: 0\n" + + " | | |- Total failed: 0\n" + + " | | `- File list: /var/log/auth.log\n" + + " | `- Actions\n" + + " | |- Currently banned: 3\n" + + " | |- Total banned: 3\n" + + " | `- Banned IP list: 192.168.0.1 10.2.2.1 2001:db8::1\n" + + " `- Jail: exim4\n" + + " |- Filter\n" + + " | |- Currently failed: 3\n" + + " | |- Total failed: 6\n" + + " | `- File list: /var/log/exim4/mainlog\n" + + " `- Actions\n" + + " |- Currently banned: 0\n" + + " |- Total banned: 0\n" + + " `- Banned IP list: " + ) + self.assertEqual(self.b.beautify(response), output) + def testFlushLogs(self): self.b.setInputCmd(["flushlogs"]) self.assertEqual(self.b.beautify("rolled over"), "logs: rolled over") diff --git a/fail2ban/tests/servertestcase.py b/fail2ban/tests/servertestcase.py index a9e9ed43..47b6437e 100644 --- a/fail2ban/tests/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -620,6 +620,19 @@ class Transmitter(TransmitterBase): ["set", self.jailName, "addignoreregex", 50])[0], 1) + _JAIL_STATUS = [ + ('Filter', [ + ('Currently failed', 0), + ('Total failed', 0), + ('File list', [])] + ), + ('Actions', [ + ('Currently banned', 0), + ('Total banned', 0), + ('Banned IP list', [])] + ) + ] + def testStatus(self): jails = [self.jailName] self.assertEqual(self.transm.proceed(["status"]), @@ -628,59 +641,24 @@ class Transmitter(TransmitterBase): jails.append("TestJail2") self.assertEqual(self.transm.proceed(["status"]), (0, [('Number of jail', len(jails)), ('Jail list', ", ".join(jails))])) + self.assertEqual(self.transm.proceed(["status", "--all"]), + (0, [('Number of jail', len(jails)), ('Jail list', ", ".join(jails)), + {"TestJail1": self._JAIL_STATUS, "TestJail2": self._JAIL_STATUS} + ])) def testJailStatus(self): self.assertEqual(self.transm.proceed(["status", self.jailName]), - (0, - [ - ('Filter', [ - ('Currently failed', 0), - ('Total failed', 0), - ('File list', [])] - ), - ('Actions', [ - ('Currently banned', 0), - ('Total banned', 0), - ('Banned IP list', [])] - ) - ] - ) + (0, self._JAIL_STATUS) ) def testJailStatusBasic(self): self.assertEqual(self.transm.proceed(["status", self.jailName, "basic"]), - (0, - [ - ('Filter', [ - ('Currently failed', 0), - ('Total failed', 0), - ('File list', [])] - ), - ('Actions', [ - ('Currently banned', 0), - ('Total banned', 0), - ('Banned IP list', [])] - ) - ] - ) + (0, self._JAIL_STATUS) ) def testJailStatusBasicKwarg(self): self.assertEqual(self.transm.proceed(["status", self.jailName, "INVALID"]), - (0, - [ - ('Filter', [ - ('Currently failed', 0), - ('Total failed', 0), - ('File list', [])] - ), - ('Actions', [ - ('Currently banned', 0), - ('Total banned', 0), - ('Banned IP list', [])] - ) - ] - ) + (0, self._JAIL_STATUS) ) def testJailStatusCymru(self):