diff --git a/config/action.d/smtp.py b/config/action.d/smtp.py index 90b32055..1a9a7496 100644 --- a/config/action.d/smtp.py +++ b/config/action.d/smtp.py @@ -1,3 +1,21 @@ +# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*- +# vi: set ft=python sts=4 ts=4 sw=4 noet : + +# This file is part of Fail2Ban. +# +# Fail2Ban is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Fail2Ban is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Fail2Ban; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import sys import socket @@ -52,94 +70,112 @@ Matches for %(ip)s for jail %(jailname)s: """ class SMTPAction(ActionBase): + """Fail2Ban action which sends emails to inform on jail starting, + stopping and bans. + """ - def __init__( - self, jail, name, host="localhost", user=None, password=None, - sendername="Fail2Ban", sender="fail2ban", dest="root", matches=None): + def __init__( + self, jail, actionname, host="localhost", user=None, password=None, + sendername="Fail2Ban", sender="fail2ban", dest="root", matches=None): + """SMTPAction is initiliased with a Fail2Ban `jail` instance, and + an `actionname`. `host` is the SMTP host, which can include port + number in "host:port" format. `user` and `password` can be specified + for SMTP authentication. `sendername` and `sender` is the email + address and readable name. `dest` is the email address of intended + recipient(s) in comma delimited format. `matches` can be one of + `matches`, `ipmatches` and `ipjailmatches` (see man jail.conf.5). + """ - super(SMTPAction, self).__init__(jail, name) + super(SMTPAction, self).__init__(jail, actionname) - self.host = host - #TODO: self.ssl = ssl + self.host = host + #TODO: self.ssl = ssl - self.user = user - self.password =password + self.user = user + self.password =password - self.fromname = sendername - self.fromaddr = sender - self.toaddr = dest + self.fromname = sendername + self.fromaddr = sender + self.toaddr = dest - self.matches = matches + self.matches = matches - self.message_values = CallingMap( - jailname = self.jail.getName(), # Doesn't change - hostname = socket.gethostname, - bantime = self.jail.getAction().getBanTime, - ) + self.message_values = CallingMap( + jailname = self._jail.getName(), # Doesn't change + hostname = socket.gethostname, + bantime = self._jail.getAction().getBanTime, + ) - def _sendMessage(self, subject, text): - msg = MIMEText(text) - msg['Subject'] = subject - msg['From'] = formataddr((self.fromname, self.fromaddr)) - msg['To'] = self.toaddr - msg['Date'] = formatdate() + def _sendMessage(self, subject, text): + msg = MIMEText(text) + msg['Subject'] = subject + msg['From'] = formataddr((self.fromname, self.fromaddr)) + msg['To'] = self.toaddr + msg['Date'] = formatdate() - smtp = smtplib.SMTP() - try: - self.logSys.debug("Connected to SMTP '%s', response: %i: %s", - self.host, *smtp.connect(self.host)) - if self.user and self.password: - smtp.login(self.user, self.password) - failed_recipients = smtp.sendmail( - self.fromaddr, self.toaddr, msg.as_string()) - except smtplib.SMTPConnectError: - self.logSys.error("Error connecting to host '%s'", self.host) - raise - except smtplib.SMTPAuthenticationError: - self.logSys.error( - "Failed to authenticate with host '%s' user '%s'", - self.host, self.user) - raise - except smtplib.SMTPException: - self.logSys.error( - "Error sending mail to host '%s' from '%s' to '%s'", - self.host, self.fromaddr, self.toaddr) - raise - else: - if failed_recipients: - self.logSys.warning( - "Email to '%s' failed to following recipients: %r", - self.toaddr, failed_recipients) - self.logSys.debug("Email '%s' successfully sent", subject) - finally: - try: - self.logSys.debug("Disconnected from '%s', response %i: %s", - self.host, *smtp.quit()) - except smtplib.SMTPServerDisconnected: - pass # Not connected + smtp = smtplib.SMTP() + try: + self._logSys.debug("Connected to SMTP '%s', response: %i: %s", + self.host, *smtp.connect(self.host)) + if self.user and self.password: + smtp.login(self.user, self.password) + failed_recipients = smtp.sendmail( + self.fromaddr, self.toaddr, msg.as_string()) + except smtplib.SMTPConnectError: + self._logSys.error("Error connecting to host '%s'", self.host) + raise + except smtplib.SMTPAuthenticationError: + self._logSys.error( + "Failed to authenticate with host '%s' user '%s'", + self.host, self.user) + raise + except smtplib.SMTPException: + self._logSys.error( + "Error sending mail to host '%s' from '%s' to '%s'", + self.host, self.fromaddr, self.toaddr) + raise + else: + if failed_recipients: + self._logSys.warning( + "Email to '%s' failed to following recipients: %r", + self.toaddr, failed_recipients) + self._logSys.debug("Email '%s' successfully sent", subject) + finally: + try: + self._logSys.debug("Disconnected from '%s', response %i: %s", + self.host, *smtp.quit()) + except smtplib.SMTPServerDisconnected: + pass # Not connected - def execActionStart(self): - self._sendMessage( - "[Fail2Ban] %(jailname)s: started on %(hostname)s" % - self.message_values, - messages['start'] % self.message_values) + def start(self): + """Sends email to recipients informing that the jail has started. + """ + self._sendMessage( + "[Fail2Ban] %(jailname)s: started on %(hostname)s" % + self.message_values, + messages['start'] % self.message_values) - def execActionStop(self): - self._sendMessage( - "[Fail2Ban] %(jailname)s: stopped on %(hostname)s" % - self.message_values, - messages['stop'] % self.message_values) + def stop(self): + """Sends email to recipients informing that the jail has stopped. + """ + self._sendMessage( + "[Fail2Ban] %(jailname)s: stopped on %(hostname)s" % + self.message_values, + messages['stop'] % self.message_values) - def execActionBan(self, aInfo): - aInfo.update(self.message_values) - message = "".join([ - messages['ban']['head'], - messages['ban'].get(self.matches, ""), - messages['ban']['tail'] - ]) - self._sendMessage( - "[Fail2Ban] %(jailname)s: banned %(ip)s from %(hostname)s" % - aInfo, - message % aInfo) + def ban(self, aInfo): + """Sends email to recipients informing that ban has occurred and + has associated information about the ban. + """ + aInfo.update(self.message_values) + message = "".join([ + messages['ban']['head'], + messages['ban'].get(self.matches, ""), + messages['ban']['tail'] + ]) + self._sendMessage( + "[Fail2Ban] %(jailname)s: banned %(ip)s from %(hostname)s" % + aInfo, + message % aInfo) Action = SMTPAction diff --git a/fail2ban/client/actionreader.py b/fail2ban/client/actionreader.py index 4452abe5..e9e4fa3e 100644 --- a/fail2ban/client/actionreader.py +++ b/fail2ban/client/actionreader.py @@ -59,22 +59,20 @@ class ActionReader(DefinitionInitConfigReader): head = ["set", self._jailName] stream = list() stream.append(head + ["addaction", self._name]) + head.extend(["action", self._name]) for opt in self._opts: if opt == "actionstart": - stream.append(head + ["actionstart", self._name, self._opts[opt]]) + stream.append(head + ["actionstart", self._opts[opt]]) elif opt == "actionstop": - stream.append(head + ["actionstop", self._name, self._opts[opt]]) + stream.append(head + ["actionstop", self._opts[opt]]) elif opt == "actioncheck": - stream.append(head + ["actioncheck", self._name, self._opts[opt]]) + stream.append(head + ["actioncheck", self._opts[opt]]) elif opt == "actionban": - stream.append(head + ["actionban", self._name, self._opts[opt]]) + stream.append(head + ["actionban", self._opts[opt]]) elif opt == "actionunban": - stream.append(head + ["actionunban", self._name, self._opts[opt]]) + stream.append(head + ["actionunban", self._opts[opt]]) if self._initOpts: - if "timeout" in self._initOpts: - stream.append(head + ["timeout", self._name, self._opts["timeout"]]) - # cInfo for p in self._initOpts: - stream.append(head + ["setcinfo", self._name, p, self._initOpts[p]]) + stream.append(head + [p, self._initOpts[p]]) return stream diff --git a/fail2ban/client/beautifier.py b/fail2ban/client/beautifier.py index 4bfa9345..e6476a21 100644 --- a/fail2ban/client/beautifier.py +++ b/fail2ban/client/beautifier.py @@ -165,7 +165,23 @@ class Beautifier: 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) + msg += ", ".join(response) + elif inC[2] == "actionproperties": + if len(response) == 0: + msg = "No properties for jail %s action %s" % ( + inC[1], inC[3]) + else: + msg = "The jail %s action %s has the following " \ + "properties:\n" % (inC[1], inC[3]) + msg += ", ".join(response) + elif inC[2] == "actionmethods": + if len(response) == 0: + msg = "No methods for jail %s action %s" % ( + inC[1], inC[3]) + else: + msg = "The jail %s action %s has the following " \ + "methods:\n" % (inC[1], inC[3]) + msg += ", ".join(response) except Exception: logSys.warning("Beautifier error. Please report the error") logSys.error("Beautify " + `response` + " with " + `self.__inputCmd` + diff --git a/fail2ban/protocol.py b/fail2ban/protocol.py index 0ec03b9e..417780e5 100644 --- a/fail2ban/protocol.py +++ b/fail2ban/protocol.py @@ -76,16 +76,18 @@ protocol = [ ["set unbanip ", "manually Unban in "], ["set maxretry ", "sets the number of failures before banning the host for "], ["set maxlines ", "sets the number of to buffer for regex search for "], -["set addaction [ ]", "adds a new action named for . Optionally for a python based action, a and can be specified"], -["set delaction ", "removes the action from "], -["set setcinfo ", "sets for of the action for "], -["set delcinfo ", "removes for the action for "], -["set timeout ", "sets as the command timeout in seconds for the action for "], -["set actionstart ", "sets the start command of the action for "], -["set actionstop ", "sets the stop command of the action for "], -["set actioncheck ", "sets the check command of the action for "], -["set actionban ", "sets the ban command of the action for "], -["set actionunban ", "sets the unban command of the action for "], +["set addaction [ ]", "adds a new action named for . Optionally for a Python based action, a and can be specified, else will be a Command Action"], +["set delaction ", "removes the action from "], +["", "COMMAND ACTION CONFIGURATION", ""], +["set action actionstart ", "sets the start command of the action for "], +["set action actionstop ", "sets the stop command of the action for "], +["set action actioncheck ", "sets the check command of the action for "], +["set action actionban ", "sets the ban command of the action for "], +["set action actionunban ", "sets the unban command of the action for "], +["set action timeout ", "sets as the command timeout in seconds for the action for "], +["", "GENERAL ACTION CONFIGURATION", ""], +["set action ", "sets the of for the action for "], +["set action [ ]", "calls the with for the action for "], ['', "JAIL INFORMATION", ""], ["get logpath", "gets the list of the monitored files for "], ["get logencoding ", "gets the of the log files for "], @@ -102,13 +104,17 @@ protocol = [ ["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 "], -["get actionban ", "gets the ban command for the action for "], -["get actionunban ", "gets the unban command for the action for "], -["get cinfo ", "gets the value for for the action for "], -["get timeout ", "gets the command timeout in seconds for the action for "], +["", "COMMAND ACTION INFORMATION",""], +["get action actionstart", "gets the start command for the action for "], +["get action actionstop", "gets the stop command for the action for "], +["get action actioncheck", "gets the check command for the action for "], +["get action actionban", "gets the ban command for the action for "], +["get action actionunban", "gets the unban command for the action for "], +["get action timeout", "gets the command timeout in seconds for the action for "], +["", "GENERAL ACTION INFORMATION", ""], +["get actionproperties ", "gets a list of properties for the action for "], +["get actionmethods ", "gets a list of methods for the action for "], +["get action ", "gets the value of for the action for "], ] ## @@ -125,15 +131,15 @@ def printFormatted(): print firstHeading = True first = True - if len(m[0]) > MARGIN+INDENT: + if len(m[0]) >= MARGIN: m[1] = ' ' * WIDTH + m[1] for n in textwrap.wrap(m[1], WIDTH, drop_whitespace=False): if first: - line = ' ' * INDENT + m[0] + ' ' * (MARGIN - len(m[0])) + n + line = ' ' * INDENT + m[0] + ' ' * (MARGIN - len(m[0])) + n.strip() first = False else: - line = ' ' * (INDENT + MARGIN) + n - print line.rstrip() + line = ' ' * (INDENT + MARGIN) + n.strip() + print line ## # Prints the protocol in a "mediawiki" format. diff --git a/fail2ban/server/action.py b/fail2ban/server/action.py index 01010bb0..7c6e3386 100644 --- a/fail2ban/server/action.py +++ b/fail2ban/server/action.py @@ -49,6 +49,14 @@ signame = dict((num, name) for name, num in signal.__dict__.iteritems() if name.startswith("SIG")) class CallingMap(MutableMapping): + """Calling Map behaves similar to a standard python dictionary, + with the exception that any values which are callable, are called + and the result of the callable is returned. + No error handling is in place, such that any errors raised in the + callable will raised as usual. + Actual dictionary is stored in property `data`, and can be accessed + to obtain original callable values. + """ def __init__(self, *args, **kwargs): self.data = dict(*args, **kwargs) def __getitem__(self, key): @@ -66,262 +74,203 @@ class CallingMap(MutableMapping): def __len__(self): return len(self.data) -## -# Execute commands. -# -# This class reads the failures from the Jail queue and decide if an -# action has to be taken. A BanManager take care of the banned IP -# addresses. - class ActionBase(object): + """Action Base is a base definition of what methods need to be in + place to create a python based action for fail2ban. This class can + be inherited from to ease implementation, but is not required as + long as the following required methods/properties are implemented: + - __init__(jail, actionname) + - start() + - stop() + - ban(aInfo) + - unban(aInfo) + - actionname + """ __metaclass__ = ABCMeta @classmethod def __subclasshook__(cls, C): required = ( - "getName", - "execActionStart", - "execActionStop", - "execActionBan", - "execActionUnban", + "start", + "stop", + "ban", + "unban", ) for method in required: if not callable(getattr(C, method, None)): return False return True - def __init__(self, jail, name): + def __init__(self, jail, actionname): + """Should initialise the action class with `jail` being the Jail + object the action belongs to, `actionname` being the name assigned + to the action, and `kwargs` being all other args that have been + specified with jail.conf or on the fail2ban-client. + """ self._jail = jail - self._name = name + self._actionname = actionname self._logSys = logging.getLogger( '%s.%s' % (__name__, self.__class__.__name__)) @property - def jail(self): - return self._jail + def actionname(self): + """The name of the action, which should not change in the + lifetime of the action.""" + return self._actionname - @property - def logSys(self): - return self._logSys - - ## - # Returns the action name. - # - # @return the name of the action - - def getName(self): - return self._name - - name = property(getName) - - def execActionStart(self): + def start(self): + """Executed when the jail/action starts.""" pass - def execActionBan(self, aInfo): + def stop(self): + """Executed when the jail/action stops or action is deleted. + """ pass - def execActionUnban(self, aInfo): + def ban(self, aInfo): + """Executed when a ban occurs. `aInfo` is a dictionary which + includes information in relation to the ban. + """ pass - def execActionStop(self): + def unban(self, aInfo): + """Executed when a ban expires. `aInfo` as per execActionBan. + """ pass class CommandAction(ActionBase): + """A Fail2Ban action which executes commands with Python's + subprocess module. This is the default type of action which + Fail2Ban uses. + """ - def __init__(self, name): - super(CommandAction, self).__init__(None, name) - self.__timeout = 60 - self.__cInfo = dict() + def __init__(self, jail, actionname): + super(CommandAction, self).__init__(jail, actionname) + self.timeout = 60 ## Command executed in order to initialize the system. - self.__actionStart = '' + self.actionstart = '' ## Command executed when an IP address gets banned. - self.__actionBan = '' + self.actionban = '' ## Command executed when an IP address gets removed. - self.__actionUnban = '' + self.actionunban = '' ## Command executed in order to check requirements. - self.__actionCheck = '' + self.actioncheck = '' ## Command executed in order to stop the system. - self.__actionStop = '' - logSys.debug("Created Action") + self.actionstop = '' + self._logSys.debug("Created %s" % self.__class__) @classmethod def __subclasshook__(cls, C): return NotImplemented # Standard checks - - ## - # Sets the timeout period for commands. - # - # @param timeout timeout period in seconds - - def setTimeout(self, timeout): - self.__timeout = int(timeout) - logSys.debug("Set action %s timeout = %i" % (self.getName(), timeout)) - - ## - # Returns the action timeout period for commands. - # - # @return the timeout period in seconds - - def getTimeout(self): - return self.__timeout - - ## - # Sets a "CInfo". - # - # CInfo are statically defined properties. They can be definied by - # the user and are used to set e-mail addresses, port, host or - # anything that should not change during the life of the server. - # - # @param key the property name - # @param value the property value - - def setCInfo(self, key, value): - self.__cInfo[key] = value - - ## - # Returns a "CInfo". - # - # @param key the property name - - def getCInfo(self, key): - return self.__cInfo[key] - - ## - # Removes a "CInfo". - # - # @param key the property name - - def delCInfo(self, key): - del self.__cInfo[key] - - ## - # Set the "start" command. - # - # @param value the command - - def setActionStart(self, value): - self.__actionStart = value - logSys.debug("Set actionStart = %s" % value) - - ## - # Get the "start" command. - # - # @return the command - - def getActionStart(self): - return self.__actionStart - - ## - # Executes the action "start" command. - # - # Replaces the tags in the action command with value of "cInfo" - # and executes the resulting command. - # - # @return True if the command succeeded - - def execActionStart(self): - if self.__cInfo: - if not self.substituteRecursiveTags(self.__cInfo): - logSys.error("Cinfo/definitions contain self referencing definitions and cannot be resolved") - return False - startCmd = self.replaceTag(self.__actionStart, self.__cInfo) - return self.executeCmd(startCmd, self.__timeout) - - ## - # Set the "ban" command. - # - # @param value the command - - def setActionBan(self, value): - self.__actionBan = value - logSys.debug("Set actionBan = %s" % value) - - ## - # Get the "ban" command. - # - # @return the command - - def getActionBan(self): - return self.__actionBan - - ## - # Executes the action "ban" command. - # - # @return True if the command succeeded - - def execActionBan(self, aInfo): - return self.__processCmd(self.__actionBan, aInfo) - - ## - # Set the "unban" command. - # - # @param value the command - - def setActionUnban(self, value): - self.__actionUnban = value - logSys.debug("Set actionUnban = %s" % value) - - ## - # Get the "unban" command. - # - # @return the command - - def getActionUnban(self): - return self.__actionUnban - - ## - # Executes the action "unban" command. - # - # @return True if the command succeeded - - def execActionUnban(self, aInfo): - return self.__processCmd(self.__actionUnban, aInfo) - - ## - # Set the "check" command. - # - # @param value the command - - def setActionCheck(self, value): - self.__actionCheck = value - logSys.debug("Set actionCheck = %s" % value) - - ## - # Get the "check" command. - # - # @return the command - - def getActionCheck(self): - return self.__actionCheck - - ## - # Set the "stop" command. - # - # @param value the command - - def setActionStop(self, value): - self.__actionStop = value - logSys.debug("Set actionStop = %s" % value) - - ## - # Get the "stop" command. - # - # @return the command - - def getActionStop(self): - return self.__actionStop - - ## - # Executes the action "stop" command. - # - # Replaces the tags in the action command with value of "cInfo" - # and executes the resulting command. - # - # @return True if the command succeeded - - def execActionStop(self): - stopCmd = self.replaceTag(self.__actionStop, self.__cInfo) - return self.executeCmd(stopCmd, self.__timeout) + + @property + def timeout(self): + """Timeout period in seconds for execution of commands + """ + return self._timeout + @timeout.setter + def timeout(self, timeout): + self._timeout = int(timeout) + self._logSys.debug("Set action %s timeout = %i" % + (self.actionname, self.timeout)) + + @property + def _properties(self): + return dict( + (key, getattr(self, key)) + for key in dir(self) + if not key.startswith("_") and not callable(getattr(self, key))) + + @property + def actionstart(self): + """The command executed on start of the jail/action. + """ + return self._actionstart + @actionstart.setter + def actionstart(self, value): + self._actionstart = value + self._logSys.debug("Set actionstart = %s" % value) + + def start(self): + """Executes the "actionstart" command. + Replace the tags in the action command with actions properties + and executes the resulting command. + """ + if (self._properties and + not self.substituteRecursiveTags(self._properties)): + self._logSys.error( + "properties contain self referencing definitions " + "and cannot be resolved") + raise RuntimeError("Error starting action") + startCmd = self.replaceTag(self.actionstart, self._properties) + if not self.executeCmd(startCmd, self.timeout): + raise RuntimeError("Error starting action") + + @property + def actionban(self): + """The command used when a ban occurs. + """ + return self._actionban + @actionban.setter + def actionban(self, value): + self._actionban = value + self._logSys.debug("Set actionban = %s" % value) + + def ban(self, aInfo): + """Executes the "actionban" command. + Replace the tags in the action command with actions properties + and ban information, and executes the resulting command. + """ + if not self._processCmd(self.actionban, aInfo): + raise RuntimeError("Error banning %(ip)s" % aInfo) + + @property + def actionunban(self): + """The command used when an unban occurs. + """ + return self._actionunban + @actionunban.setter + def actionunban(self, value): + self._actionunban = value + self._logSys.debug("Set actionunban = %s" % value) + + def unban(self, aInfo): + """Executes the "actionunban" command. + Replace the tags in the action command with actions properties + and ban information, and executes the resulting command. + """ + if not self._processCmd(self.actionunban, aInfo): + raise RuntimeError("Error unbanning %(ip)s" % aInfo) + + @property + def actioncheck(self): + """The command used to check correct environment in place for + ban action to take place. + """ + return self._actioncheck + @actioncheck.setter + def actioncheck(self, value): + self._actioncheck = value + self._logSys.debug("Set actioncheck = %s" % value) + + @property + def actionstop(self): + """The command executed when the jail/actions stops. + """ + return self._actionstop + @actionstop.setter + def actionstop(self, value): + self._actionstop = value + self._logSys.debug("Set actionstop = %s" % value) + + def stop(self): + """Executes the "actionstop" command. + Replace the tags in the action command with actions properties + and executes the resulting command. + """ + stopCmd = self.replaceTag(self.actionstop, self._properties) + if not self.executeCmd(stopCmd, self.timeout): + raise RuntimeError("Error stopping action") ## # Sort out tag definitions within other tags @@ -397,21 +346,21 @@ class CommandAction(ActionBase): # @param aInfo Dynamic properties # @return True if the command succeeded - def __processCmd(self, cmd, aInfo = None): + def _processCmd(self, cmd, aInfo = None): """ Executes an OS command. """ if cmd == "": - logSys.debug("Nothing to do") + self._logSys.debug("Nothing to do") return True - checkCmd = self.replaceTag(self.__actionCheck, self.__cInfo) - if not self.executeCmd(checkCmd, self.__timeout): - logSys.error("Invariant check failed. Trying to restore a sane" + - " environment") - self.execActionStop() - self.execActionStart() - if not self.executeCmd(checkCmd, self.__timeout): - logSys.fatal("Unable to restore environment") + checkCmd = self.replaceTag(self.actioncheck, self._properties) + if not self.executeCmd(checkCmd, self.timeout): + self._logSys.error( + "Invariant check failed. Trying to restore a sane environment") + self.stop() + self.start() + if not self.executeCmd(checkCmd, self.timeout): + self._logSys.fatal("Unable to restore environment") return False # Replace tags @@ -421,9 +370,9 @@ class CommandAction(ActionBase): realCmd = cmd # Replace static fields - realCmd = self.replaceTag(realCmd, self.__cInfo) + realCmd = self.replaceTag(realCmd, self._properties) - return self.executeCmd(realCmd, self.__timeout) + return self.executeCmd(realCmd, self.timeout) ## # Executes a command. @@ -495,5 +444,6 @@ class CommandAction(ActionBase): if msg: logSys.info("HINT on %i: %s" % (retcode, msg % locals())) - return False + return False + raise RuntimeError("Command execution failed: %s" % realCmd) diff --git a/fail2ban/server/actions.py b/fail2ban/server/actions.py index c63a08e0..72db2947 100644 --- a/fail2ban/server/actions.py +++ b/fail2ban/server/actions.py @@ -66,10 +66,10 @@ class Actions(JailThread): def addAction(self, name, pythonModule=None, initOpts=None): # Check is action name already exists - if name in [action.getName() for action in self.__actions]: + if name in [action.actionname for action in self.__actions]: raise ValueError("Action %s already exists" % name) if pythonModule is None: - action = CommandAction(name) + action = CommandAction(self.jail, name) else: pythonModuleName = os.path.basename(pythonModule.strip(".py")) customActionModule = imp.load_source( @@ -91,7 +91,7 @@ class Actions(JailThread): def delAction(self, name): for action in self.__actions: - if action.getName() == name: + if action.actionname == name: self.__actions.remove(action) return raise KeyError("Invalid Action name: %s" % name) @@ -106,7 +106,7 @@ class Actions(JailThread): def getAction(self, name): for action in self.__actions: - if action.getName() == name: + if action.actionname == name: return action raise KeyError("Invalid Action name") @@ -116,9 +116,7 @@ class Actions(JailThread): # @return The last defined action. def getLastAction(self): - action = self.__actions.pop() - self.__actions.append(action) - return action + return self.__actions[-1] ## # Returns the list of actions @@ -169,10 +167,10 @@ class Actions(JailThread): self.setActive(True) for action in self.__actions: try: - action.execActionStart() + action.start() except Exception as e: logSys.error("Failed to start jail '%s' action '%s': %s", - self.jail.getName(), action.getName(), e) + self.jail.getName(), action.actionname, e) while self._isActive(): if not self.getIdle(): #logSys.debug(self.jail.getName() + ": action") @@ -185,10 +183,10 @@ class Actions(JailThread): self.__flushBan() for action in self.__actions: try: - action.execActionStop() + action.stop() except Exception as e: logSys.error("Failed to stop jail '%s' action '%s': %s", - self.jail.getName(), action.getName(), e) + self.jail.getName(), action.actionname, e) logSys.debug(self.jail.getName() + ": action terminated") return True @@ -225,11 +223,11 @@ class Actions(JailThread): logSys.warning("[%s] Ban %s" % (self.jail.getName(), aInfo["ip"])) for action in self.__actions: try: - action.execActionBan(aInfo) + action.ban(aInfo) except Exception as e: logSys.error( "Failed to execute ban jail '%s' action '%s': %s", - self.jail.getName(), action.getName(), e) + self.jail.getName(), action.actionname, e) return True else: logSys.info("[%s] %s already banned" % (self.jail.getName(), @@ -270,11 +268,11 @@ class Actions(JailThread): logSys.warning("[%s] Unban %s" % (self.jail.getName(), aInfo["ip"])) for action in self.__actions: try: - action.execActionUnban(aInfo) + action.unban(aInfo) except Exception as e: logSys.error( "Failed to execute unban jail '%s' action '%s': %s", - self.jail.getName(), action.getName(), e) + self.jail.getName(), action.actionname, e) ## diff --git a/fail2ban/server/server.py b/fail2ban/server/server.py index bc7bc4ba..41393db4 100644 --- a/fail2ban/server/server.py +++ b/fail2ban/server/server.py @@ -291,26 +291,8 @@ class Server: def delAction(self, name, value): self.__jails.getAction(name).delAction(value) - def setCInfo(self, name, actionName, key, value): - action = self.__jails.getAction(name).getAction(actionName) - if isinstance(action, CommandAction): - action.setCInfo(key, value) - else: - raise TypeError("%s is not a CommandAction" % actionName) - - def getCInfo(self, name, actionName, key): - action = self.__jails.getAction(name).getAction(actionName) - if isinstance(action, CommandAction): - return action.getCInfo(key) - else: - raise TypeError("%s is not a CommandAction" % actionName) - - def delCInfo(self, name, actionName, key): - action = self.__jails.getAction(name).getAction(actionName) - if isinstance(action, CommandAction): - action.delCInfo(key) - else: - raise TypeError("%s is not a CommandAction" % actionName) + def getAction(self, name, value): + return self.__jails.getAction(name).getAction(value) def setBanTime(self, name, value): self.__jails.getAction(name).setBanTime(value) @@ -324,90 +306,6 @@ class Server: def getBanTime(self, name): return self.__jails.getAction(name).getBanTime() - def setActionStart(self, name, actionName, value): - action = self.__jails.getAction(name).getAction(actionName) - if isinstance(action, CommandAction): - action.setActionStart(value) - else: - raise TypeError("%s is not a CommandAction" % actionName) - - def getActionStart(self, name, actionName): - action = self.__jails.getAction(name).getAction(actionName) - if isinstance(action, CommandAction): - return action.getActionStart() - else: - raise TypeError("%s is not a CommandAction" % actionName) - - def setActionStop(self, name, actionName, value): - action = self.__jails.getAction(name).getAction(actionName) - if isinstance(action, CommandAction): - action.setActionStop(value) - else: - raise TypeError("%s is not a CommandAction" % actionName) - - def getActionStop(self, name, actionName): - action = self.__jails.getAction(name).getAction(actionName) - if isinstance(action, CommandAction): - return action.getActionStop() - else: - raise TypeError("%s is not a CommandAction" % actionName) - - def setActionCheck(self, name, actionName, value): - action = self.__jails.getAction(name).getAction(actionName) - if isinstance(action, CommandAction): - action.setActionCheck(value) - else: - raise TypeError("%s is not a CommandAction" % actionName) - - def getActionCheck(self, name, actionName): - action = self.__jails.getAction(name).getAction(actionName) - if isinstance(action, CommandAction): - return action.getActionCheck() - else: - raise TypeError("%s is not a CommandAction" % actionName) - - def setActionBan(self, name, actionName, value): - action = self.__jails.getAction(name).getAction(actionName) - if isinstance(action, CommandAction): - action.setActionBan(value) - else: - raise TypeError("%s is not a CommandAction" % actionName) - - def getActionBan(self, name, actionName): - action = self.__jails.getAction(name).getAction(actionName) - if isinstance(action, CommandAction): - return action.getActionBan() - else: - raise TypeError("%s is not a CommandAction" % actionName) - - def setActionUnban(self, name, actionName, value): - action = self.__jails.getAction(name).getAction(actionName) - if isinstance(action, CommandAction): - action.setActionUnban(value) - else: - raise TypeError("%s is not a CommandAction" % actionName) - - def getActionUnban(self, name, actionName): - action = self.__jails.getAction(name).getAction(actionName) - if isinstance(action, CommandAction): - return action.getActionUnban() - else: - raise TypeError("%s is not a CommandAction" % actionName) - - def setActionTimeout(self, name, actionName, value): - action = self.__jails.getAction(name).getAction(actionName) - if isinstance(action, CommandAction): - action.setTimeout(value) - else: - raise TypeError("%s is not a CommandAction" % actionName) - - def getActionTimeout(self, name, actionName): - action = self.__jails.getAction(name).getAction(actionName) - if isinstance(action, CommandAction): - return action.getTimeout() - else: - raise TypeError("%s is not a CommandAction" % actionName) - # Status def status(self): try: diff --git a/fail2ban/server/transmitter.py b/fail2ban/server/transmitter.py index 10ada98a..6a7d4de1 100644 --- a/fail2ban/server/transmitter.py +++ b/fail2ban/server/transmitter.py @@ -233,52 +233,22 @@ class Transmitter: if len(command) > 3: args.extend([command[3], json.loads(command[4])]) self.__server.addAction(name, *args) - return self.__server.getLastAction(name).getName() + return self.__server.getLastAction(name).actionname elif command[1] == "delaction": value = command[2] self.__server.delAction(name, value) return None - elif command[1] == "setcinfo": - act = command[2] - key = command[3] - value = " ".join(command[4:]) - self.__server.setCInfo(name, act, key, value) - return self.__server.getCInfo(name, act, key) - elif command[1] == "delcinfo": - act = command[2] - key = command[3] - self.__server.delCInfo(name, act, key) - return None - elif command[1] == "actionstart": - act = command[2] - value = " ".join(command[3:]) - self.__server.setActionStart(name, act, value) - return self.__server.getActionStart(name, act) - elif command[1] == "actionstop": - act = command[2] - value = " ".join(command[3:]) - self.__server.setActionStop(name, act, value) - return self.__server.getActionStop(name, act) - elif command[1] == "actioncheck": - act = command[2] - value = " ".join(command[3:]) - self.__server.setActionCheck(name, act, value) - return self.__server.getActionCheck(name, act) - elif command[1] == "actionban": - act = command[2] - value = " ".join(command[3:]) - self.__server.setActionBan(name, act, value) - return self.__server.getActionBan(name, act) - elif command[1] == "actionunban": - act = command[2] - value = " ".join(command[3:]) - self.__server.setActionUnban(name, act, value) - return self.__server.getActionUnban(name, act) - elif command[1] == "timeout": - act = command[2] - value = int(command[3]) - self.__server.setActionTimeout(name, act, value) - return self.__server.getActionTimeout(name, act) + elif command[1] == "action": + actionname = command[2] + actionkey = command[3] + action = self.__server.getAction(name, actionname) + if callable(getattr(action, actionkey, None)): + actionvalue = json.loads(command[4]) if len(command)>4 else {} + return getattr(action, actionkey)(**actionvalue) + else: + actionvalue = command[4] + setattr(action, actionkey, actionvalue) + return getattr(action, actionkey) raise Exception("Invalid command (no set action or not yet implemented)") def __commandGet(self, command): @@ -330,31 +300,28 @@ class Transmitter: elif command[1] == "bantime": return self.__server.getBanTime(name) elif command[1] == "actions": - return self.__server.getActions(name) + return [action.actionname + for action in self.__server.getActions(name)] elif command[1] == "addaction": - return self.__server.getLastAction(name).getName() - elif command[1] == "actionstart": - act = command[2] - return self.__server.getActionStart(name, act) - elif command[1] == "actionstop": - act = command[2] - return self.__server.getActionStop(name, act) - elif command[1] == "actioncheck": - act = command[2] - return self.__server.getActionCheck(name, act) - elif command[1] == "actionban": - act = command[2] - return self.__server.getActionBan(name, act) - 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) - elif command[1] == "timeout": - act = command[2] - return self.__server.getActionTimeout(name, act) + return self.__server.getLastAction(name).actionname + elif command[1] == "action": + actionname = command[2] + actionvalue = command[3] + action = self.__server.getAction(name, actionname) + return getattr(action, actionvalue) + elif command[1] == "actionproperties": + actionname = command[2] + action = self.__server.getAction(name, actionname) + return [ + key for key in dir(action) + if not key.startswith("_") and + not callable(getattr(action, key))] + elif command[1] == "actionmethods": + actionname = command[2] + action = self.__server.getAction(name, actionname) + return [ + key for key in dir(action) + if not key.startswith("_") and callable(getattr(action, key))] raise Exception("Invalid command (no get action or not yet implemented)") def status(self, command): diff --git a/fail2ban/tests/actionstestcase.py b/fail2ban/tests/actionstestcase.py index 7c0609de..a133b8fa 100644 --- a/fail2ban/tests/actionstestcase.py +++ b/fail2ban/tests/actionstestcase.py @@ -49,11 +49,11 @@ class ExecuteActions(LogCaptureTestCase): def defaultActions(self): self.__actions.addAction('ip') self.__ip = self.__actions.getAction('ip') - self.__ip.setActionStart('echo ip start 64 >> "%s"' % self.__tmpfilename ) - self.__ip.setActionBan('echo ip ban >> "%s"' % self.__tmpfilename ) - self.__ip.setActionUnban('echo ip unban >> "%s"' % self.__tmpfilename ) - self.__ip.setActionCheck('echo ip check >> "%s"' % self.__tmpfilename ) - self.__ip.setActionStop('echo ip stop >> "%s"' % self.__tmpfilename ) + self.__ip.actionstart = 'echo ip start 64 >> "%s"' % self.__tmpfilename + self.__ip.actionban = 'echo ip ban >> "%s"' % self.__tmpfilename + self.__ip.actionunban = 'echo ip unban >> "%s"' % self.__tmpfilename + self.__ip.actioncheck = 'echo ip check >> "%s"' % self.__tmpfilename + self.__ip.actionstop = 'echo ip stop >> "%s"' % self.__tmpfilename def testActionsManipulation(self): self.__actions.addAction('test') diff --git a/fail2ban/tests/actiontestcase.py b/fail2ban/tests/actiontestcase.py index b625fe94..6d4636a8 100644 --- a/fail2ban/tests/actiontestcase.py +++ b/fail2ban/tests/actiontestcase.py @@ -31,17 +31,17 @@ from fail2ban.server.action import CommandAction, CallingMap from fail2ban.tests.utils import LogCaptureTestCase -class ExecuteAction(LogCaptureTestCase): +class CommandActionTest(LogCaptureTestCase): def setUp(self): """Call before every test case.""" - self.__action = CommandAction("Test") + self.__action = CommandAction(None, "Test") LogCaptureTestCase.setUp(self) def tearDown(self): """Call after every test case.""" LogCaptureTestCase.tearDown(self) - self.__action.execActionStop() + self.__action.stop() def testSubstituteRecursiveTags(self): aInfo = { @@ -105,62 +105,61 @@ class ExecuteAction(LogCaptureTestCase): CallingMap(callme=lambda: int("a"))), "abc") def testExecuteActionBan(self): - self.__action.setActionStart("touch /tmp/fail2ban.test") - self.assertEqual(self.__action.getActionStart(), "touch /tmp/fail2ban.test") - self.__action.setActionStop("rm -f /tmp/fail2ban.test") - self.assertEqual(self.__action.getActionStop(), 'rm -f /tmp/fail2ban.test') - self.__action.setActionBan("echo -n") - self.assertEqual(self.__action.getActionBan(), 'echo -n') - self.__action.setActionCheck("[ -e /tmp/fail2ban.test ]") - self.assertEqual(self.__action.getActionCheck(), '[ -e /tmp/fail2ban.test ]') - self.__action.setActionUnban("true") - self.assertEqual(self.__action.getActionUnban(), 'true') + self.__action.actionstart = "touch /tmp/fail2ban.test" + self.assertEqual(self.__action.actionstart, "touch /tmp/fail2ban.test") + self.__action.actionstop = "rm -f /tmp/fail2ban.test" + self.assertEqual(self.__action.actionstop, 'rm -f /tmp/fail2ban.test') + self.__action.actionban = "echo -n" + self.assertEqual(self.__action.actionban, 'echo -n') + self.__action.actioncheck = "[ -e /tmp/fail2ban.test ]" + self.assertEqual(self.__action.actioncheck, '[ -e /tmp/fail2ban.test ]') + self.__action.actionunban = "true" + self.assertEqual(self.__action.actionunban, 'true') self.assertFalse(self._is_logged('returned')) # no action was actually executed yet - self.assertTrue(self.__action.execActionBan(None)) + self.__action.ban({'ip': None}) self.assertTrue(self._is_logged('Invariant check failed')) self.assertTrue(self._is_logged('returned successfully')) def testExecuteActionEmptyUnban(self): - self.__action.setActionUnban("") - self.assertTrue(self.__action.execActionUnban(None)) + self.__action.actionunban = "" + self.__action.unban({}) self.assertTrue(self._is_logged('Nothing to do')) def testExecuteActionStartCtags(self): - self.__action.setCInfo("HOST","192.0.2.0") - self.__action.setActionStart("touch /tmp/fail2ban.test.") - self.__action.setActionStop("rm -f /tmp/fail2ban.test.") - self.__action.setActionCheck("[ -e /tmp/fail2ban.test.192.0.2.0 ]") - self.assertTrue(self.__action.execActionStart()) + self.__action.HOST = "192.0.2.0" + self.__action.actionstart = "touch /tmp/fail2ban.test." + self.__action.actionstop = "rm -f /tmp/fail2ban.test." + self.__action.actioncheck = "[ -e /tmp/fail2ban.test.192.0.2.0 ]" + self.__action.start() def testExecuteActionCheckRestoreEnvironment(self): - self.__action.setActionStart("") - self.__action.setActionStop("rm -f /tmp/fail2ban.test") - self.__action.setActionBan("rm /tmp/fail2ban.test") - self.__action.setActionCheck("[ -e /tmp/fail2ban.test ]") - self.assertFalse(self.__action.execActionBan(None)) + self.__action.actionstart = "" + self.__action.actionstop = "rm -f /tmp/fail2ban.test" + self.__action.actionban = "rm /tmp/fail2ban.test" + self.__action.actioncheck = "[ -e /tmp/fail2ban.test ]" + self.assertRaises(RuntimeError, self.__action.ban, {'ip': None}) self.assertTrue(self._is_logged('Unable to restore environment')) def testExecuteActionChangeCtags(self): - self.__action.setCInfo("ROST","192.0.2.0") - self.assertEqual(self.__action.getCInfo("ROST"),"192.0.2.0") - self.__action.delCInfo("ROST") - self.assertRaises(KeyError, self.__action.getCInfo, "ROST") + self.assertRaises(AttributeError, getattr, self.__action, "ROST") + self.__action.ROST = "192.0.2.0" + self.assertEqual(self.__action.ROST,"192.0.2.0") def testExecuteActionUnbanAinfo(self): aInfo = { 'ABC': "123", } - self.__action.setActionBan("touch /tmp/fail2ban.test.123") - self.__action.setActionUnban("rm /tmp/fail2ban.test.") - self.assertTrue(self.__action.execActionBan(None)) - self.assertTrue(self.__action.execActionUnban(aInfo)) + self.__action.actionban = "touch /tmp/fail2ban.test.123" + self.__action.actionunban = "rm /tmp/fail2ban.test." + self.__action.ban(aInfo) + self.__action.unban(aInfo) def testExecuteActionStartEmpty(self): - self.__action.setActionStart("") - self.assertTrue(self.__action.execActionStart()) + self.__action.actionstart = "" + self.__action.start() self.assertTrue(self._is_logged('Nothing to do')) def testExecuteIncorrectCmd(self): @@ -169,7 +168,9 @@ class ExecuteAction(LogCaptureTestCase): def testExecuteTimeout(self): stime = time.time() - CommandAction.executeCmd('sleep 60', timeout=2) # Should take a minute + # Should take a minute + self.assertRaises( + RuntimeError, CommandAction.executeCmd, 'sleep 60', timeout=2) self.assertAlmostEqual(time.time() - stime, 2, places=0) self.assertTrue(self._is_logged('sleep 60 -- timed out after 2 seconds')) self.assertTrue(self._is_logged('sleep 60 -- killed with SIGTERM')) diff --git a/fail2ban/tests/clientreadertestcase.py b/fail2ban/tests/clientreadertestcase.py index 99cb3379..577ab42a 100644 --- a/fail2ban/tests/clientreadertestcase.py +++ b/fail2ban/tests/clientreadertestcase.py @@ -353,13 +353,18 @@ class JailsReaderTest(LogCaptureTestCase): ['set', 'brokenaction', 'addaction', 'brokenaction'], ['set', 'brokenaction', - 'actionban', + 'action', 'brokenaction', + 'actionban', 'hit with big stick '], - ['set', 'brokenaction', 'actionstop', 'brokenaction', ''], - ['set', 'brokenaction', 'actionstart', 'brokenaction', ''], - ['set', 'brokenaction', 'actionunban', 'brokenaction', ''], - ['set', 'brokenaction', 'actioncheck', 'brokenaction', ''], + ['set', 'brokenaction', 'action', 'brokenaction', + 'actionstop', ''], + ['set', 'brokenaction', 'action', 'brokenaction', + 'actionstart', ''], + ['set', 'brokenaction', 'action', 'brokenaction', + 'actionunban', ''], + ['set', 'brokenaction', 'action', 'brokenaction', + 'actioncheck', ''], ['add', 'parse_to_end_of_jail.conf', 'auto'], ['set', 'parse_to_end_of_jail.conf', 'usedns', 'warn'], ['set', 'parse_to_end_of_jail.conf', 'maxretry', 3], @@ -486,7 +491,7 @@ class JailsReaderTest(LogCaptureTestCase): self.assertTrue('blocktype' in action._initOpts) # Verify that we have a call to set it up blocktype_present = False - target_command = [ 'set', jail_name, 'setcinfo', action_name, 'blocktype' ] + target_command = ['set', jail_name, 'action', action_name, 'blocktype'] for command in commands: if (len(command) > 5 and command[:5] == target_command): diff --git a/fail2ban/tests/files/action.d/action.py b/fail2ban/tests/files/action.d/action.py index f0535b76..83e7a43b 100644 --- a/fail2ban/tests/files/action.d/action.py +++ b/fail2ban/tests/files/action.d/action.py @@ -3,20 +3,26 @@ from fail2ban.server.action import ActionBase class TestAction(ActionBase): - def __init__(self, jail, name, opt1, opt2=None): - super(TestAction, self).__init__(jail, name) - self.logSys.debug("%s initialised" % self.__class__.__name__) + def __init__(self, jail, actionname, opt1, opt2=None): + super(TestAction, self).__init__(jail, actionname) + self._logSys.debug("%s initialised" % self.__class__.__name__) + self.opt1 = opt1 + self.opt2 = opt2 + self._opt3 = "Hello" - def execActionStart(self): - self.logSys.debug("%s action start" % self.__class__.__name__) + def start(self): + self._logSys.debug("%s action start" % self.__class__.__name__) - def execActionStop(self): - self.logSys.debug("%s action stop" % self.__class__.__name__) + def stop(self): + self._logSys.debug("%s action stop" % self.__class__.__name__) - def execActionBan(self, aInfo): - self.logSys.debug("%s action ban" % self.__class__.__name__) + def ban(self, aInfo): + self._logSys.debug("%s action ban" % self.__class__.__name__) - def execActionUnban(self, aInfo): - self.logSys.debug("%s action unban" % self.__class__.__name__) + def unban(self, aInfo): + self._logSys.debug("%s action unban" % self.__class__.__name__) + + def testmethod(self, text): + return "%s %s %s" % (self._opt3, text, self.opt1) Action = TestAction diff --git a/fail2ban/tests/servertestcase.py b/fail2ban/tests/servertestcase.py index 21c6c371..99928500 100644 --- a/fail2ban/tests/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -523,40 +523,40 @@ class Transmitter(TransmitterBase): (0, action)) self.assertEqual( self.transm.proceed( - ["get", self.jailName, "actions"])[1][0].getName(), + ["get", self.jailName, "actions"])[1][0], action) for cmd, value in zip(cmdList, cmdValueList): self.assertEqual( self.transm.proceed( - ["set", self.jailName, cmd, action, value]), + ["set", self.jailName, "action", action, cmd, value]), (0, value)) for cmd, value in zip(cmdList, cmdValueList): self.assertEqual( - self.transm.proceed(["get", self.jailName, cmd, action]), + self.transm.proceed(["get", self.jailName, "action", action, cmd]), (0, value)) self.assertEqual( self.transm.proceed( - ["set", self.jailName, "setcinfo", action, "KEY", "VALUE"]), + ["set", self.jailName, "action", action, "KEY", "VALUE"]), (0, "VALUE")) self.assertEqual( self.transm.proceed( - ["get", self.jailName, "cinfo", action, "KEY"]), + ["get", self.jailName, "action", action, "KEY"]), (0, "VALUE")) self.assertEqual( self.transm.proceed( - ["get", self.jailName, "cinfo", action, "InvalidKey"])[0], + ["get", self.jailName, "action", action, "InvalidKey"])[0], 1) self.assertEqual( self.transm.proceed( - ["set", self.jailName, "delcinfo", action, "KEY"]), - (0, None)) + ["get", self.jailName, "action", action, "actionname"]), + (0, action)) self.assertEqual( self.transm.proceed( - ["set", self.jailName, "timeout", action, "10"]), + ["set", self.jailName, "action", action, "timeout", "10"]), (0, 10)) self.assertEqual( self.transm.proceed( - ["get", self.jailName, "timeout", action]), + ["get", self.jailName, "action", action, "timeout"]), (0, 10)) self.assertEqual( self.transm.proceed(["set", self.jailName, "delaction", action]), @@ -564,23 +564,42 @@ class Transmitter(TransmitterBase): self.assertEqual( self.transm.proceed( ["set", self.jailName, "delaction", "Doesn't exist"])[0],1) + + def testPythonActionMethodsAndProperties(self): + action = "TestCaseAction" self.assertEqual( self.transm.proceed(["set", self.jailName, "addaction", action, os.path.join(TEST_FILES_DIR, "action.d", "action.py"), '{"opt1": "value"}']), (0, action)) - for cmd, value in zip(cmdList, cmdValueList): - self.assertTrue( - isinstance(self.transm.proceed( - ["set", self.jailName, cmd, action, value])[1], - TypeError), - "set %s for python action did not raise TypeError" % cmd) - for cmd, value in zip(cmdList, cmdValueList): - self.assertTrue( - isinstance(self.transm.proceed( - ["get", self.jailName, cmd, action])[1], - TypeError), - "get %s for python action did not raise TypeError" % cmd) + self.assertEqual( + sorted(self.transm.proceed(["get", self.jailName, + "actionproperties", action])), + [0, ['actionname', 'opt1', 'opt2']]) + self.assertEqual( + self.transm.proceed(["get", self.jailName, "action", action, + "opt1"]), + (0, 'value')) + self.assertEqual( + self.transm.proceed(["get", self.jailName, "action", action, + "opt2"]), + (0, None)) + self.assertEqual( + sorted(self.transm.proceed(["get", self.jailName, "actionmethods", + action])), + [0, ['ban', 'start', 'stop', 'testmethod', 'unban']]) + self.assertEqual( + self.transm.proceed(["set", self.jailName, "action", action, + "testmethod", '{"text": "world!"}']), + (0, 'Hello world! value')) + self.assertEqual( + self.transm.proceed(["set", self.jailName, "action", action, + "opt1", "another value"]), + (0, 'another value')) + self.assertEqual( + self.transm.proceed(["set", self.jailName, "action", action, + "testmethod", '{"text": "world!"}']), + (0, 'Hello world! another value')) def testNOK(self): self.assertEqual(self.transm.proceed(["INVALID", "COMMAND"])[0],1) diff --git a/fail2ban/tests/utils.py b/fail2ban/tests/utils.py index b1f26bab..cf9e70cc 100644 --- a/fail2ban/tests/utils.py +++ b/fail2ban/tests/utils.py @@ -172,7 +172,7 @@ def gatherTests(regexps=None, no_network=False): tests.addTest(unittest.makeSuite(servertestcase.Transmitter)) tests.addTest(unittest.makeSuite(servertestcase.JailTests)) tests.addTest(unittest.makeSuite(servertestcase.RegexTests)) - tests.addTest(unittest.makeSuite(actiontestcase.ExecuteAction)) + tests.addTest(unittest.makeSuite(actiontestcase.CommandActionTest)) tests.addTest(unittest.makeSuite(actionstestcase.ExecuteActions)) # FailManager tests.addTest(unittest.makeSuite(failmanagertestcase.AddFailure)) diff --git a/man/jail.conf.5 b/man/jail.conf.5 index 2fef6aa7..9131c4b5 100644 --- a/man/jail.conf.5 +++ b/man/jail.conf.5 @@ -113,7 +113,7 @@ uses systemd python library to access the systemd journal. Specifying \fBlogpath will try to use the following backends, in order: pyinotify, gamin, polling .PP .SS Actions -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, and in the case of python actions, the ".py" file extension is stripped. Where multiple of the same action are to be used, the \fBactname\fR option can be assigned to the action to avoid duplication e.g.: +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, and in the case of Python actions, the ".py" file extension is stripped. Where multiple of the same action are to be used, the \fBactname\fR option can be assigned to the action to avoid duplication e.g.: .PP .nf [ssh-iptables-ipset] @@ -163,7 +163,7 @@ two commands to be executed. actionban = iptables -I fail2ban- --source -j DROP echo ip=, match=, time=