Merge pull request #189 from kwirk/multiaction

Allow creation of multiple of the same action for a filter -- use actname option. Close #37
pull/214/merge
Yaroslav Halchenko 12 years ago
commit e019ab784c

@ -457,23 +457,15 @@ ignoreip = 168.192.0.1
# Miscelaneous # Miscelaneous
# #
# Multiple jails, 1 per protocol, are necessary ATM: [asterisk]
# see https://github.com/fail2ban/fail2ban/issues/37
[asterisk-tcp]
filter = asterisk
port = 5060,5061 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 logpath = /var/log/asterisk/messages
maxretry = 10 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: # To log wrong MySQL access attempts add to /etc/my.cnf:
# log-error=/var/log/mysqld.log # log-error=/var/log/mysqld.log

@ -43,27 +43,38 @@ class ActionReader(DefinitionInitConfigReader):
["string", "actionunban", ""], ["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): def read(self):
return ConfigReader.read(self, os.path.join("action.d", self._file)) return ConfigReader.read(self, os.path.join("action.d", self._file))
def convert(self): def convert(self):
head = ["set", self._name] head = ["set", self._jailName]
stream = list() stream = list()
stream.append(head + ["addaction", self._file]) stream.append(head + ["addaction", self._name])
for opt in self._opts: for opt in self._opts:
if opt == "actionstart": if opt == "actionstart":
stream.append(head + ["actionstart", self._file, self._opts[opt]]) stream.append(head + ["actionstart", self._name, self._opts[opt]])
elif opt == "actionstop": elif opt == "actionstop":
stream.append(head + ["actionstop", self._file, self._opts[opt]]) stream.append(head + ["actionstop", self._name, self._opts[opt]])
elif opt == "actioncheck": elif opt == "actioncheck":
stream.append(head + ["actioncheck", self._file, self._opts[opt]]) stream.append(head + ["actioncheck", self._name, self._opts[opt]])
elif opt == "actionban": elif opt == "actionban":
stream.append(head + ["actionban", self._file, self._opts[opt]]) stream.append(head + ["actionban", self._name, self._opts[opt]])
elif opt == "actionunban": elif opt == "actionunban":
stream.append(head + ["actionunban", self._file, self._opts[opt]]) stream.append(head + ["actionunban", self._name, self._opts[opt]])
# cInfo # cInfo
if self._initOpts: if self._initOpts:
for p in 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 return stream

@ -132,6 +132,12 @@ class Beautifier:
msg = msg + "|- [" + str(c) + "]: " + ip + "\n" msg = msg + "|- [" + str(c) + "]: " + ip + "\n"
c += 1 c += 1
msg = msg + "`- [" + str(c) + "]: " + response[len(response)-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: except Exception:
logSys.warning("Beautifier error. Please report the error") logSys.warning("Beautifier error. Please report the error")
logSys.error("Beautify " + `response` + " with " + `self.__inputCmd` + logSys.error("Beautify " + `response` + " with " + `self.__inputCmd` +

@ -145,7 +145,7 @@ class DefinitionInitConfigReader(ConfigReader):
def __init__(self, file_, jailName, initOpts, **kwargs): def __init__(self, file_, jailName, initOpts, **kwargs):
ConfigReader.__init__(self, **kwargs) ConfigReader.__init__(self, **kwargs)
self._file = file_ self._file = file_
self._name = jailName self._jailName = jailName
self._initOpts = initOpts self._initOpts = initOpts
def setFile(self, fileName): def setFile(self, fileName):
@ -154,11 +154,11 @@ class DefinitionInitConfigReader(ConfigReader):
def getFile(self): def getFile(self):
return self.__file return self.__file
def setName(self, name): def setJailName(self, jailName):
self._name = name self._jailName = jailName
def getName(self): def getJailName(self):
return self._name return self._jailName
def read(self): def read(self):
return ConfigReader.read(self, self._file) return ConfigReader.read(self, self._file)

@ -50,14 +50,14 @@ class FilterReader(DefinitionInitConfigReader):
for regex in self._opts[opt].split('\n'): for regex in self._opts[opt].split('\n'):
# Do not send a command if the rule is empty. # Do not send a command if the rule is empty.
if regex != '': if regex != '':
stream.append(["set", self._name, "addfailregex", regex]) stream.append(["set", self._jailName, "addfailregex", regex])
elif opt == "ignoreregex": elif opt == "ignoreregex":
for regex in self._opts[opt].split('\n'): for regex in self._opts[opt].split('\n'):
# Do not send a command if the rule is empty. # Do not send a command if the rule is empty.
if regex != '': if regex != '':
stream.append(["set", self._name, "addignoreregex", regex]) stream.append(["set", self._jailName, "addignoreregex", regex])
if self._initOpts: if self._initOpts:
if 'maxlines' in 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 return stream

@ -90,6 +90,7 @@ protocol = [
["get <JAIL> maxretry", "gets the number of failures allowed for <JAIL>"], ["get <JAIL> maxretry", "gets the number of failures allowed for <JAIL>"],
["get <JAIL> maxlines", "gets the number of lines to buffer for <JAIL>"], ["get <JAIL> maxlines", "gets the number of lines to buffer for <JAIL>"],
["get <JAIL> addaction", "gets the last action which has been added for <JAIL>"], ["get <JAIL> addaction", "gets the last action which has been added for <JAIL>"],
["get <JAIL> actions", "gets a list of actions for <JAIL>"],
["get <JAIL> actionstart <ACT>", "gets the start command for the action <ACT> for <JAIL>"], ["get <JAIL> actionstart <ACT>", "gets the start command for the action <ACT> for <JAIL>"],
["get <JAIL> actionstop <ACT>", "gets the stop command for the action <ACT> for <JAIL>"], ["get <JAIL> actionstop <ACT>", "gets the stop command for the action <ACT> for <JAIL>"],
["get <JAIL> actioncheck <ACT>", "gets the check command for the action <ACT> for <JAIL>"], ["get <JAIL> actioncheck <ACT>", "gets the check command for the action <ACT> for <JAIL>"],

@ -65,6 +65,9 @@ class Actions(JailThread):
# @param name The action name # @param name The action name
def addAction(self, 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) action = Action(name)
self.__actions.append(action) self.__actions.append(action)
@ -104,6 +107,14 @@ class Actions(JailThread):
self.__actions.append(action) self.__actions.append(action)
return action return action
##
# Returns the list of actions
#
# @return list of actions
def getActions(self):
return self.__actions
## ##
# Set the ban time. # Set the ban time.
# #

@ -236,6 +236,9 @@ class Server:
def getLastAction(self, name): def getLastAction(self, name):
return self.__jails.getAction(name).getLastAction() return self.__jails.getAction(name).getLastAction()
def getActions(self, name):
return self.__jails.getAction(name).getActions()
def delAction(self, name, value): def delAction(self, name, value):
self.__jails.getAction(name).delAction(value) self.__jails.getAction(name).delAction(value)

@ -265,6 +265,8 @@ class Transmitter:
# Action # Action
elif command[1] == "bantime": elif command[1] == "bantime":
return self.__server.getBanTime(name) return self.__server.getBanTime(name)
elif command[1] == "actions":
return self.__server.getActions(name)
elif command[1] == "addaction": elif command[1] == "addaction":
return self.__server.getLastAction(name).getName() return self.__server.getLastAction(name).getName()
elif command[1] == "actionstart": elif command[1] == "actionstart":

@ -302,3 +302,28 @@ class JailsReaderTest(unittest.TestCase):
configurator._Configurator__jails.setBaseDir('/tmp') configurator._Configurator__jails.setBaseDir('/tmp')
self.assertEqual(configurator._Configurator__jails.getBaseDir(), '/tmp') self.assertEqual(configurator._Configurator__jails.getBaseDir(), '/tmp')
self.assertEqual(configurator.getBaseDir(), CONFIG_DIR) 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)

@ -440,8 +440,12 @@ class Transmitter(TransmitterBase):
self.transm.proceed(["set", self.jailName, "addaction", action]), self.transm.proceed(["set", self.jailName, "addaction", action]),
(0, action)) (0, action))
self.assertEqual( self.assertEqual(
self.transm.proceed(["get", self.jailName, "addaction", action]), self.transm.proceed(["get", self.jailName, "addaction"]),
(0, action)) (0, action))
self.assertEqual(
self.transm.proceed(
["get", self.jailName, "actions"])[1][0].getName(),
action)
for cmd, value in zip(cmdList, cmdValueList): for cmd, value in zip(cmdList, cmdValueList):
self.assertEqual( self.assertEqual(
self.transm.proceed( self.transm.proceed(

@ -59,7 +59,15 @@ The following options are applicable to all jails. Their meaning is described in
\fBbackend\fR \fBbackend\fR
.TP .TP
\fBusedns\fR \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" .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. Action files specify which commands are executed to ban and unban an IP address. They are located under \fI/etc/fail2ban/action.d\fR.

Loading…
Cancel
Save