RF: Refactor actions further, include removing server proxy interface

This allows direct setting of action properties and calling of methods
from the fail2ban-client if so required.
pull/549/head
Steven Hiscocks 2014-01-03 17:04:49 +00:00
parent 414c5e1146
commit 80d6f74ee8
16 changed files with 511 additions and 610 deletions

View File

@ -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

View File

@ -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

View File

@ -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` +

View File

@ -76,16 +76,18 @@ protocol = [
["set <JAIL> unbanip <IP>", "manually Unban <IP> in <JAIL>"],
["set <JAIL> maxretry <RETRY>", "sets the number of failures <RETRY> before banning the host for <JAIL>"],
["set <JAIL> maxlines <LINES>", "sets the number of <LINES> to buffer for regex search for <JAIL>"],
["set <JAIL> addaction <ACT> [<PYTHONFILE> <JSONOPTS>]", "adds a new action named <NAME> for <JAIL>. Optionally for a python based action, a <PYTHONFILE> and <JSONOPTS> can be specified"],
["set <JAIL> delaction <ACT>", "removes the action <NAME> from <JAIL>"],
["set <JAIL> setcinfo <ACT> <KEY> <VALUE>", "sets <VALUE> for <KEY> of the action <NAME> for <JAIL>"],
["set <JAIL> delcinfo <ACT> <KEY>", "removes <KEY> for the action <NAME> for <JAIL>"],
["set <JAIL> timeout <ACT> <TIMEOUT>", "sets <TIMEOUT> as the command timeout in seconds for the action <ACT> for <JAIL>"],
["set <JAIL> actionstart <ACT> <CMD>", "sets the start command <CMD> of the action <ACT> for <JAIL>"],
["set <JAIL> actionstop <ACT> <CMD>", "sets the stop command <CMD> of the action <ACT> for <JAIL>"],
["set <JAIL> actioncheck <ACT> <CMD>", "sets the check command <CMD> of the action <ACT> for <JAIL>"],
["set <JAIL> actionban <ACT> <CMD>", "sets the ban command <CMD> of the action <ACT> for <JAIL>"],
["set <JAIL> actionunban <ACT> <CMD>", "sets the unban command <CMD> of the action <ACT> for <JAIL>"],
["set <JAIL> addaction <ACT>[ <PYTHONFILE> <JSONKWARGS>]", "adds a new action named <NAME> for <JAIL>. Optionally for a Python based action, a <PYTHONFILE> and <JSONKWARGS> can be specified, else will be a Command Action"],
["set <JAIL> delaction <ACT>", "removes the action <ACT> from <JAIL>"],
["", "COMMAND ACTION CONFIGURATION", ""],
["set <JAIL> action <ACT> actionstart <CMD>", "sets the start command <CMD> of the action <ACT> for <JAIL>"],
["set <JAIL> action <ACT> actionstop <CMD>", "sets the stop command <CMD> of the action <ACT> for <JAIL>"],
["set <JAIL> action <ACT> actioncheck <CMD>", "sets the check command <CMD> of the action <ACT> for <JAIL>"],
["set <JAIL> action <ACT> actionban <CMD>", "sets the ban command <CMD> of the action <ACT> for <JAIL>"],
["set <JAIL> action <ACT> actionunban <CMD>", "sets the unban command <CMD> of the action <ACT> for <JAIL>"],
["set <JAIL> action <ACT> timeout <TIMEOUT>", "sets <TIMEOUT> as the command timeout in seconds for the action <ACT> for <JAIL>"],
["", "GENERAL ACTION CONFIGURATION", ""],
["set <JAIL> action <ACT> <PROPERTY> <VALUE>", "sets the <VALUE> of <PROPERTY> for the action <ACT> for <JAIL>"],
["set <JAIL> action <ACT> <METHOD>[ <JSONKWARGS>]", "calls the <METHOD> with <JSONKWARGS> for the action <ACT> for <JAIL>"],
['', "JAIL INFORMATION", ""],
["get <JAIL> logpath", "gets the list of the monitored files for <JAIL>"],
["get <JAIL> logencoding <ENCODING>", "gets the <ENCODING> of the log files for <JAIL>"],
@ -102,13 +104,17 @@ protocol = [
["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> actions", "gets a list of actions 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> 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>"],
["get <JAIL> timeout <ACT>", "gets the command timeout in seconds for the action <ACT> for <JAIL>"],
["", "COMMAND ACTION INFORMATION",""],
["get <JAIL> action <ACT> actionstart", "gets the start command for the action <ACT> for <JAIL>"],
["get <JAIL> action <ACT> actionstop", "gets the stop command for the action <ACT> for <JAIL>"],
["get <JAIL> action <ACT> actioncheck", "gets the check command for the action <ACT> for <JAIL>"],
["get <JAIL> action <ACT> actionban", "gets the ban command for the action <ACT> for <JAIL>"],
["get <JAIL> action <ACT> actionunban", "gets the unban command for the action <ACT> for <JAIL>"],
["get <JAIL> action <ACT> timeout", "gets the command timeout in seconds for the action <ACT> for <JAIL>"],
["", "GENERAL ACTION INFORMATION", ""],
["get <JAIL> actionproperties <ACT>", "gets a list of properties for the action <ACT> for <JAIL>"],
["get <JAIL> actionmethods <ACT>", "gets a list of methods for the action <ACT> for <JAIL>"],
["get <JAIL> action <ACT> <PROPERTY>", "gets the value of <PROPERTY> for the action <ACT> for <JAIL>"],
]
##
@ -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.

View File

@ -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)

View File

@ -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)
##

View File

@ -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:

View File

@ -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):

View File

@ -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 <ip> >> "%s"' % self.__tmpfilename )
self.__ip.setActionUnban('echo ip unban <ip> >> "%s"' % self.__tmpfilename )
self.__ip.setActionCheck('echo ip check <ip> >> "%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 <ip> >> "%s"' % self.__tmpfilename
self.__ip.actionunban = 'echo ip unban <ip> >> "%s"' % self.__tmpfilename
self.__ip.actioncheck = 'echo ip check <ip> >> "%s"' % self.__tmpfilename
self.__ip.actionstop = 'echo ip stop >> "%s"' % self.__tmpfilename
def testActionsManipulation(self):
self.__actions.addAction('test')

View File

@ -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.<HOST>")
self.__action.setActionStop("rm -f /tmp/fail2ban.test.<HOST>")
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.<HOST>"
self.__action.actionstop = "rm -f /tmp/fail2ban.test.<HOST>"
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.<ABC>")
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.<ABC>"
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'))

View File

@ -353,13 +353,18 @@ class JailsReaderTest(LogCaptureTestCase):
['set', 'brokenaction', 'addaction', 'brokenaction'],
['set',
'brokenaction',
'actionban',
'action',
'brokenaction',
'actionban',
'hit with big stick <ip>'],
['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):

View File

@ -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

View File

@ -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)

View File

@ -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))

View File

@ -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-<name> --source <ip> -j DROP
echo ip=<ip>, match=<match>, time=<time> >> /var/log/fail2ban.log
.TP
Python based actions can also be used, where the file name must be \fI[actionname].py\fR. The python file must contain a variable \fIAction\fR which points to python class. This class must implement a minimum interface as described by \fIfail2ban.server.action.ActionBase\fR, which can be inherited from to ease implementation.
Python based actions can also be used, where the file name must be \fI[actionname].py\fR. The Python file must contain a variable \fIAction\fR which points to Python class. This class must implement a minimum interface as described by \fIfail2ban.server.action.ActionBase\fR, which can be inherited from to ease implementation.
.SS "Action Tags"
The following tags are substituted in the actionban, actionunban and actioncheck (when called before actionban/actionunban) commands.

View File

@ -120,7 +120,8 @@ setup(
glob("config/filter.d/*.conf")
),
('/etc/fail2ban/action.d',
glob("config/action.d/*.*")
glob("config/action.d/*.conf") +
glob("config/action.d/*.py")
),
('/etc/fail2ban/fail2ban.d',
''