diff --git a/config/jail.conf b/config/jail.conf index c6eea0ef..1e663a22 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -457,23 +457,15 @@ ignoreip = 168.192.0.1 # Miscelaneous # -# Multiple jails, 1 per protocol, are necessary ATM: -# see https://github.com/fail2ban/fail2ban/issues/37 -[asterisk-tcp] +[asterisk] -filter = asterisk port = 5060,5061 -protocol = tcp -logpath = /var/log/asterisk/messages -maxretry = 10 - -[asterisk-udp] - -filter = asterisk -port = 5060,5061 -protocol = udp logpath = /var/log/asterisk/messages maxretry = 10 +# Astrix requires both tcp and udp +action = %(banaction)s[name=%(__name__)s-tcp, port="%(port)s", protocol="tcp", chain="%(chain)s", actname=%(banaction)s-tcp] + %(banaction)s[name=%(__name__)s-udp, port="%(port)s", protocol="udp", chain="%(chain)s", actname=%(banaction)s-udp] + %(mta)s-whois[name=%(__name__)s, dest="%(destemail)s"] # To log wrong MySQL access attempts add to /etc/my.cnf: # log-error=/var/log/mysqld.log diff --git a/fail2ban/client/actionreader.py b/fail2ban/client/actionreader.py index ca4080d0..7ae3c45b 100644 --- a/fail2ban/client/actionreader.py +++ b/fail2ban/client/actionreader.py @@ -43,27 +43,38 @@ class ActionReader(DefinitionInitConfigReader): ["string", "actionunban", ""], ] + def __init__(self, file_, jailName, initOpts, **kwargs): + self._name = initOpts.get("actname", file_) + DefinitionInitConfigReader.__init__( + self, file_, jailName, initOpts, **kwargs) + + def setName(self, name): + self._name = name + + def getName(self): + return self._name + def read(self): return ConfigReader.read(self, os.path.join("action.d", self._file)) def convert(self): - head = ["set", self._name] + head = ["set", self._jailName] stream = list() - stream.append(head + ["addaction", self._file]) + stream.append(head + ["addaction", self._name]) for opt in self._opts: if opt == "actionstart": - stream.append(head + ["actionstart", self._file, self._opts[opt]]) + stream.append(head + ["actionstart", self._name, self._opts[opt]]) elif opt == "actionstop": - stream.append(head + ["actionstop", self._file, self._opts[opt]]) + stream.append(head + ["actionstop", self._name, self._opts[opt]]) elif opt == "actioncheck": - stream.append(head + ["actioncheck", self._file, self._opts[opt]]) + stream.append(head + ["actioncheck", self._name, self._opts[opt]]) elif opt == "actionban": - stream.append(head + ["actionban", self._file, self._opts[opt]]) + stream.append(head + ["actionban", self._name, self._opts[opt]]) elif opt == "actionunban": - stream.append(head + ["actionunban", self._file, self._opts[opt]]) + stream.append(head + ["actionunban", self._name, self._opts[opt]]) # cInfo if self._initOpts: for p in self._initOpts: - stream.append(head + ["setcinfo", self._file, p, self._initOpts[p]]) + stream.append(head + ["setcinfo", self._name, p, self._initOpts[p]]) return stream diff --git a/fail2ban/client/beautifier.py b/fail2ban/client/beautifier.py index a0ff8ff3..a92e9520 100644 --- a/fail2ban/client/beautifier.py +++ b/fail2ban/client/beautifier.py @@ -132,6 +132,12 @@ class Beautifier: msg = msg + "|- [" + str(c) + "]: " + ip + "\n" c += 1 msg = msg + "`- [" + str(c) + "]: " + response[len(response)-1] + elif inC[2] == "actions": + if len(response) == 0: + msg = "No actions for jail %s" % inC[1] + else: + msg = "The jail %s has the following actions:\n" % inC[1] + msg += ", ".join(action.getName() for action in response) except Exception: logSys.warning("Beautifier error. Please report the error") logSys.error("Beautify " + `response` + " with " + `self.__inputCmd` + diff --git a/fail2ban/client/configreader.py b/fail2ban/client/configreader.py index 9a1fedaf..ac6af8f7 100644 --- a/fail2ban/client/configreader.py +++ b/fail2ban/client/configreader.py @@ -145,7 +145,7 @@ class DefinitionInitConfigReader(ConfigReader): def __init__(self, file_, jailName, initOpts, **kwargs): ConfigReader.__init__(self, **kwargs) self._file = file_ - self._name = jailName + self._jailName = jailName self._initOpts = initOpts def setFile(self, fileName): @@ -154,11 +154,11 @@ class DefinitionInitConfigReader(ConfigReader): def getFile(self): return self.__file - def setName(self, name): - self._name = name + def setJailName(self, jailName): + self._jailName = jailName - def getName(self): - return self._name + def getJailName(self): + return self._jailName def read(self): return ConfigReader.read(self, self._file) diff --git a/fail2ban/client/filterreader.py b/fail2ban/client/filterreader.py index 62424ddb..5369bc5f 100644 --- a/fail2ban/client/filterreader.py +++ b/fail2ban/client/filterreader.py @@ -50,14 +50,14 @@ class FilterReader(DefinitionInitConfigReader): for regex in self._opts[opt].split('\n'): # Do not send a command if the rule is empty. if regex != '': - stream.append(["set", self._name, "addfailregex", regex]) + stream.append(["set", self._jailName, "addfailregex", regex]) elif opt == "ignoreregex": for regex in self._opts[opt].split('\n'): # Do not send a command if the rule is empty. if regex != '': - stream.append(["set", self._name, "addignoreregex", regex]) + stream.append(["set", self._jailName, "addignoreregex", regex]) if self._initOpts: if 'maxlines' in self._initOpts: - stream.append(["set", self._name, "maxlines", self._initOpts["maxlines"]]) + stream.append(["set", self._jailName, "maxlines", self._initOpts["maxlines"]]) return stream diff --git a/fail2ban/protocol.py b/fail2ban/protocol.py index cacde4c2..334e7908 100644 --- a/fail2ban/protocol.py +++ b/fail2ban/protocol.py @@ -90,6 +90,7 @@ protocol = [ ["get maxretry", "gets the number of failures allowed for "], ["get maxlines", "gets the number of lines to buffer for "], ["get addaction", "gets the last action which has been added for "], +["get actions", "gets a list of actions for "], ["get actionstart ", "gets the start command for the action for "], ["get actionstop ", "gets the stop command for the action for "], ["get actioncheck ", "gets the check command for the action for "], diff --git a/fail2ban/server/actions.py b/fail2ban/server/actions.py index b7e58975..0f7e6b72 100644 --- a/fail2ban/server/actions.py +++ b/fail2ban/server/actions.py @@ -65,6 +65,9 @@ class Actions(JailThread): # @param name The action name def addAction(self, name): + # Check is action name already exists + if name in [action.getName() for action in self.__actions]: + raise ValueError("Action %s already exists" % name) action = Action(name) self.__actions.append(action) @@ -104,6 +107,14 @@ class Actions(JailThread): self.__actions.append(action) return action + ## + # Returns the list of actions + # + # @return list of actions + + def getActions(self): + return self.__actions + ## # Set the ban time. # diff --git a/fail2ban/server/server.py b/fail2ban/server/server.py index 632d0546..092867bf 100644 --- a/fail2ban/server/server.py +++ b/fail2ban/server/server.py @@ -236,6 +236,9 @@ class Server: def getLastAction(self, name): return self.__jails.getAction(name).getLastAction() + def getActions(self, name): + return self.__jails.getAction(name).getActions() + def delAction(self, name, value): self.__jails.getAction(name).delAction(value) diff --git a/fail2ban/server/transmitter.py b/fail2ban/server/transmitter.py index 2a4514da..b293217e 100644 --- a/fail2ban/server/transmitter.py +++ b/fail2ban/server/transmitter.py @@ -265,6 +265,8 @@ class Transmitter: # Action elif command[1] == "bantime": return self.__server.getBanTime(name) + elif command[1] == "actions": + return self.__server.getActions(name) elif command[1] == "addaction": return self.__server.getLastAction(name).getName() elif command[1] == "actionstart": diff --git a/fail2ban/tests/clientreadertestcase.py b/fail2ban/tests/clientreadertestcase.py index 62d994c8..61101aa5 100644 --- a/fail2ban/tests/clientreadertestcase.py +++ b/fail2ban/tests/clientreadertestcase.py @@ -302,3 +302,28 @@ class JailsReaderTest(unittest.TestCase): configurator._Configurator__jails.setBaseDir('/tmp') self.assertEqual(configurator._Configurator__jails.getBaseDir(), '/tmp') self.assertEqual(configurator.getBaseDir(), CONFIG_DIR) + + def testMultipleSameAction(self): + basedir = tempfile.mkdtemp("fail2ban_conf") + os.mkdir(os.path.join(basedir, "filter.d")) + os.mkdir(os.path.join(basedir, "action.d")) + open(os.path.join(basedir, "action.d", "testaction1.conf"), 'w').close() + open(os.path.join(basedir, "filter.d", "testfilter1.conf"), 'w').close() + jailfd = open(os.path.join(basedir, "jail.conf"), 'w') + jailfd.write(""" +[testjail1] +action = testaction1[actname=test1] + testaction1[actname=test2] +filter = testfilter1 +""") + jailfd.close() + jails = JailsReader(basedir=basedir) + self.assertTrue(jails.read()) + self.assertTrue(jails.getOptions()) + comm_commands = jails.convert() + + action_names = [comm[-1] for comm in comm_commands if comm[:3] == ['set', 'testjail1', 'addaction']] + + self.assertNotEqual(len(set(action_names)), 1) + + shutil.rmtree(basedir) diff --git a/fail2ban/tests/servertestcase.py b/fail2ban/tests/servertestcase.py index 385b83d4..67c7e6ae 100644 --- a/fail2ban/tests/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -440,8 +440,12 @@ class Transmitter(TransmitterBase): self.transm.proceed(["set", self.jailName, "addaction", action]), (0, action)) self.assertEqual( - self.transm.proceed(["get", self.jailName, "addaction", action]), + self.transm.proceed(["get", self.jailName, "addaction"]), (0, action)) + self.assertEqual( + self.transm.proceed( + ["get", self.jailName, "actions"])[1][0].getName(), + action) for cmd, value in zip(cmdList, cmdValueList): self.assertEqual( self.transm.proceed( diff --git a/man/jail.conf.5 b/man/jail.conf.5 index d571dc7b..3f9c0884 100644 --- a/man/jail.conf.5 +++ b/man/jail.conf.5 @@ -59,7 +59,15 @@ The following options are applicable to all jails. Their meaning is described in \fBbackend\fR .TP \fBusedns\fR - +.PP +Each jail can be configured with only a single filter, but may have multiple actions. By default, the name of a action is the action filename. In the case where multiple of the same action are to be used, the \fBactname\fR option can be assigned to the action to avoid duplicatione.g.: +.PP +.nf +[ssh-iptables-ipset] +enabled = true +action = sendmail[name=ssh, dest=john@example.com, actname=mail-john] + sendmail[name=ssh, dest=paul@example.com, actname=mail-paul] +.fi .SH "ACTION FILES" Action files specify which commands are executed to ban and unban an IP address. They are located under \fI/etc/fail2ban/action.d\fR.