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 sys
import socket import socket
@ -52,94 +70,112 @@ Matches for %(ip)s for jail %(jailname)s:
""" """
class SMTPAction(ActionBase): class SMTPAction(ActionBase):
"""Fail2Ban action which sends emails to inform on jail starting,
stopping and bans.
"""
def __init__( def __init__(
self, jail, name, host="localhost", user=None, password=None, self, jail, actionname, host="localhost", user=None, password=None,
sendername="Fail2Ban", sender="fail2ban", dest="root", matches=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 self.host = host
#TODO: self.ssl = ssl #TODO: self.ssl = ssl
self.user = user self.user = user
self.password =password self.password =password
self.fromname = sendername self.fromname = sendername
self.fromaddr = sender self.fromaddr = sender
self.toaddr = dest self.toaddr = dest
self.matches = matches self.matches = matches
self.message_values = CallingMap( self.message_values = CallingMap(
jailname = self.jail.getName(), # Doesn't change jailname = self._jail.getName(), # Doesn't change
hostname = socket.gethostname, hostname = socket.gethostname,
bantime = self.jail.getAction().getBanTime, bantime = self._jail.getAction().getBanTime,
) )
def _sendMessage(self, subject, text): def _sendMessage(self, subject, text):
msg = MIMEText(text) msg = MIMEText(text)
msg['Subject'] = subject msg['Subject'] = subject
msg['From'] = formataddr((self.fromname, self.fromaddr)) msg['From'] = formataddr((self.fromname, self.fromaddr))
msg['To'] = self.toaddr msg['To'] = self.toaddr
msg['Date'] = formatdate() msg['Date'] = formatdate()
smtp = smtplib.SMTP() smtp = smtplib.SMTP()
try: try:
self.logSys.debug("Connected to SMTP '%s', response: %i: %s", self._logSys.debug("Connected to SMTP '%s', response: %i: %s",
self.host, *smtp.connect(self.host)) self.host, *smtp.connect(self.host))
if self.user and self.password: if self.user and self.password:
smtp.login(self.user, self.password) smtp.login(self.user, self.password)
failed_recipients = smtp.sendmail( failed_recipients = smtp.sendmail(
self.fromaddr, self.toaddr, msg.as_string()) self.fromaddr, self.toaddr, msg.as_string())
except smtplib.SMTPConnectError: except smtplib.SMTPConnectError:
self.logSys.error("Error connecting to host '%s'", self.host) self._logSys.error("Error connecting to host '%s'", self.host)
raise raise
except smtplib.SMTPAuthenticationError: except smtplib.SMTPAuthenticationError:
self.logSys.error( self._logSys.error(
"Failed to authenticate with host '%s' user '%s'", "Failed to authenticate with host '%s' user '%s'",
self.host, self.user) self.host, self.user)
raise raise
except smtplib.SMTPException: except smtplib.SMTPException:
self.logSys.error( self._logSys.error(
"Error sending mail to host '%s' from '%s' to '%s'", "Error sending mail to host '%s' from '%s' to '%s'",
self.host, self.fromaddr, self.toaddr) self.host, self.fromaddr, self.toaddr)
raise raise
else: else:
if failed_recipients: if failed_recipients:
self.logSys.warning( self._logSys.warning(
"Email to '%s' failed to following recipients: %r", "Email to '%s' failed to following recipients: %r",
self.toaddr, failed_recipients) self.toaddr, failed_recipients)
self.logSys.debug("Email '%s' successfully sent", subject) self._logSys.debug("Email '%s' successfully sent", subject)
finally: finally:
try: try:
self.logSys.debug("Disconnected from '%s', response %i: %s", self._logSys.debug("Disconnected from '%s', response %i: %s",
self.host, *smtp.quit()) self.host, *smtp.quit())
except smtplib.SMTPServerDisconnected: except smtplib.SMTPServerDisconnected:
pass # Not connected pass # Not connected
def execActionStart(self): def start(self):
self._sendMessage( """Sends email to recipients informing that the jail has started.
"[Fail2Ban] %(jailname)s: started on %(hostname)s" % """
self.message_values, self._sendMessage(
messages['start'] % self.message_values) "[Fail2Ban] %(jailname)s: started on %(hostname)s" %
self.message_values,
messages['start'] % self.message_values)
def execActionStop(self): def stop(self):
self._sendMessage( """Sends email to recipients informing that the jail has stopped.
"[Fail2Ban] %(jailname)s: stopped on %(hostname)s" % """
self.message_values, self._sendMessage(
messages['stop'] % self.message_values) "[Fail2Ban] %(jailname)s: stopped on %(hostname)s" %
self.message_values,
messages['stop'] % self.message_values)
def execActionBan(self, aInfo): def ban(self, aInfo):
aInfo.update(self.message_values) """Sends email to recipients informing that ban has occurred and
message = "".join([ has associated information about the ban.
messages['ban']['head'], """
messages['ban'].get(self.matches, ""), aInfo.update(self.message_values)
messages['ban']['tail'] message = "".join([
]) messages['ban']['head'],
self._sendMessage( messages['ban'].get(self.matches, ""),
"[Fail2Ban] %(jailname)s: banned %(ip)s from %(hostname)s" % messages['ban']['tail']
aInfo, ])
message % aInfo) self._sendMessage(
"[Fail2Ban] %(jailname)s: banned %(ip)s from %(hostname)s" %
aInfo,
message % aInfo)
Action = SMTPAction Action = SMTPAction

View File

@ -59,22 +59,20 @@ class ActionReader(DefinitionInitConfigReader):
head = ["set", self._jailName] head = ["set", self._jailName]
stream = list() stream = list()
stream.append(head + ["addaction", self._name]) stream.append(head + ["addaction", self._name])
head.extend(["action", self._name])
for opt in self._opts: for opt in self._opts:
if opt == "actionstart": if opt == "actionstart":
stream.append(head + ["actionstart", self._name, self._opts[opt]]) stream.append(head + ["actionstart", self._opts[opt]])
elif opt == "actionstop": elif opt == "actionstop":
stream.append(head + ["actionstop", self._name, self._opts[opt]]) stream.append(head + ["actionstop", self._opts[opt]])
elif opt == "actioncheck": elif opt == "actioncheck":
stream.append(head + ["actioncheck", self._name, self._opts[opt]]) stream.append(head + ["actioncheck", self._opts[opt]])
elif opt == "actionban": elif opt == "actionban":
stream.append(head + ["actionban", self._name, self._opts[opt]]) stream.append(head + ["actionban", self._opts[opt]])
elif opt == "actionunban": elif opt == "actionunban":
stream.append(head + ["actionunban", self._name, self._opts[opt]]) stream.append(head + ["actionunban", self._opts[opt]])
if self._initOpts: if self._initOpts:
if "timeout" in self._initOpts:
stream.append(head + ["timeout", self._name, self._opts["timeout"]])
# cInfo
for p in self._initOpts: for p in self._initOpts:
stream.append(head + ["setcinfo", self._name, p, self._initOpts[p]]) stream.append(head + [p, self._initOpts[p]])
return stream return stream

View File

@ -165,7 +165,23 @@ class Beautifier:
msg = "No actions for jail %s" % inC[1] msg = "No actions for jail %s" % inC[1]
else: else:
msg = "The jail %s has the following actions:\n" % inC[1] 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: except Exception:
logSys.warning("Beautifier error. Please report the error") logSys.warning("Beautifier error. Please report the error")
logSys.error("Beautify " + `response` + " with " + `self.__inputCmd` + logSys.error("Beautify " + `response` + " with " + `self.__inputCmd` +

View File

@ -76,16 +76,18 @@ protocol = [
["set <JAIL> unbanip <IP>", "manually Unban <IP> in <JAIL>"], ["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> 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> 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> 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 <NAME> from <JAIL>"], ["set <JAIL> delaction <ACT>", "removes the action <ACT> from <JAIL>"],
["set <JAIL> setcinfo <ACT> <KEY> <VALUE>", "sets <VALUE> for <KEY> of the action <NAME> for <JAIL>"], ["", "COMMAND ACTION CONFIGURATION", ""],
["set <JAIL> delcinfo <ACT> <KEY>", "removes <KEY> for the action <NAME> for <JAIL>"], ["set <JAIL> action <ACT> actionstart <CMD>", "sets the start command <CMD> of the action <ACT> for <JAIL>"],
["set <JAIL> timeout <ACT> <TIMEOUT>", "sets <TIMEOUT> as the command timeout in seconds for the action <ACT> for <JAIL>"], ["set <JAIL> action <ACT> actionstop <CMD>", "sets the stop command <CMD> of the action <ACT> for <JAIL>"],
["set <JAIL> actionstart <ACT> <CMD>", "sets the start 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> actionstop <ACT> <CMD>", "sets the stop 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> actioncheck <ACT> <CMD>", "sets the check 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> actionban <ACT> <CMD>", "sets the ban 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>"],
["set <JAIL> actionunban <ACT> <CMD>", "sets the unban command <CMD> of 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", ""], ['', "JAIL INFORMATION", ""],
["get <JAIL> logpath", "gets the list of the monitored files for <JAIL>"], ["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>"], ["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> maxlines", "gets the number of lines to buffer for <JAIL>"],
["get <JAIL> addaction", "gets the last action which has been added for <JAIL>"], ["get <JAIL> addaction", "gets the last action which has been added for <JAIL>"],
["get <JAIL> actions", "gets a list of actions for <JAIL>"], ["get <JAIL> actions", "gets a list of actions for <JAIL>"],
["get <JAIL> actionstart <ACT>", "gets the start command for the action <ACT> for <JAIL>"], ["", "COMMAND ACTION INFORMATION",""],
["get <JAIL> actionstop <ACT>", "gets the stop command for the action <ACT> for <JAIL>"], ["get <JAIL> action <ACT> actionstart", "gets the start command for the action <ACT> for <JAIL>"],
["get <JAIL> actioncheck <ACT>", "gets the check command for the action <ACT> for <JAIL>"], ["get <JAIL> action <ACT> actionstop", "gets the stop command for the action <ACT> for <JAIL>"],
["get <JAIL> actionban <ACT>", "gets the ban command for the action <ACT> for <JAIL>"], ["get <JAIL> action <ACT> actioncheck", "gets the check command for the action <ACT> for <JAIL>"],
["get <JAIL> actionunban <ACT>", "gets the unban command for the action <ACT> for <JAIL>"], ["get <JAIL> action <ACT> actionban", "gets the ban 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> action <ACT> actionunban", "gets the unban command for the action <ACT> for <JAIL>"],
["get <JAIL> timeout <ACT>", "gets the command timeout in seconds 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 print
firstHeading = True firstHeading = True
first = True first = True
if len(m[0]) > MARGIN+INDENT: if len(m[0]) >= MARGIN:
m[1] = ' ' * WIDTH + m[1] m[1] = ' ' * WIDTH + m[1]
for n in textwrap.wrap(m[1], WIDTH, drop_whitespace=False): for n in textwrap.wrap(m[1], WIDTH, drop_whitespace=False):
if first: if first:
line = ' ' * INDENT + m[0] + ' ' * (MARGIN - len(m[0])) + n line = ' ' * INDENT + m[0] + ' ' * (MARGIN - len(m[0])) + n.strip()
first = False first = False
else: else:
line = ' ' * (INDENT + MARGIN) + n line = ' ' * (INDENT + MARGIN) + n.strip()
print line.rstrip() print line
## ##
# Prints the protocol in a "mediawiki" format. # 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")) for name, num in signal.__dict__.iteritems() if name.startswith("SIG"))
class CallingMap(MutableMapping): 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): def __init__(self, *args, **kwargs):
self.data = dict(*args, **kwargs) self.data = dict(*args, **kwargs)
def __getitem__(self, key): def __getitem__(self, key):
@ -66,262 +74,203 @@ class CallingMap(MutableMapping):
def __len__(self): def __len__(self):
return len(self.data) 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): 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 __metaclass__ = ABCMeta
@classmethod @classmethod
def __subclasshook__(cls, C): def __subclasshook__(cls, C):
required = ( required = (
"getName", "start",
"execActionStart", "stop",
"execActionStop", "ban",
"execActionBan", "unban",
"execActionUnban",
) )
for method in required: for method in required:
if not callable(getattr(C, method, None)): if not callable(getattr(C, method, None)):
return False return False
return True 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._jail = jail
self._name = name self._actionname = actionname
self._logSys = logging.getLogger( self._logSys = logging.getLogger(
'%s.%s' % (__name__, self.__class__.__name__)) '%s.%s' % (__name__, self.__class__.__name__))
@property @property
def jail(self): def actionname(self):
return self._jail """The name of the action, which should not change in the
lifetime of the action."""
return self._actionname
@property def start(self):
def logSys(self): """Executed when the jail/action starts."""
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):
pass pass
def execActionBan(self, aInfo): def stop(self):
"""Executed when the jail/action stops or action is deleted.
"""
pass 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 pass
def execActionStop(self): def unban(self, aInfo):
"""Executed when a ban expires. `aInfo` as per execActionBan.
"""
pass pass
class CommandAction(ActionBase): 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): def __init__(self, jail, actionname):
super(CommandAction, self).__init__(None, name) super(CommandAction, self).__init__(jail, actionname)
self.__timeout = 60 self.timeout = 60
self.__cInfo = dict()
## Command executed in order to initialize the system. ## Command executed in order to initialize the system.
self.__actionStart = '' self.actionstart = ''
## Command executed when an IP address gets banned. ## Command executed when an IP address gets banned.
self.__actionBan = '' self.actionban = ''
## Command executed when an IP address gets removed. ## Command executed when an IP address gets removed.
self.__actionUnban = '' self.actionunban = ''
## Command executed in order to check requirements. ## Command executed in order to check requirements.
self.__actionCheck = '' self.actioncheck = ''
## Command executed in order to stop the system. ## Command executed in order to stop the system.
self.__actionStop = '' self.actionstop = ''
logSys.debug("Created Action") self._logSys.debug("Created %s" % self.__class__)
@classmethod @classmethod
def __subclasshook__(cls, C): def __subclasshook__(cls, C):
return NotImplemented # Standard checks return NotImplemented # Standard checks
## @property
# Sets the timeout period for commands. def timeout(self):
# """Timeout period in seconds for execution of commands
# @param timeout timeout period in seconds """
return self._timeout
def setTimeout(self, timeout): @timeout.setter
self.__timeout = int(timeout) def timeout(self, timeout):
logSys.debug("Set action %s timeout = %i" % (self.getName(), timeout)) self._timeout = int(timeout)
self._logSys.debug("Set action %s timeout = %i" %
## (self.actionname, self.timeout))
# Returns the action timeout period for commands.
# @property
# @return the timeout period in seconds def _properties(self):
return dict(
def getTimeout(self): (key, getattr(self, key))
return self.__timeout for key in dir(self)
if not key.startswith("_") and not callable(getattr(self, key)))
##
# Sets a "CInfo". @property
# def actionstart(self):
# CInfo are statically defined properties. They can be definied by """The command executed on start of the jail/action.
# the user and are used to set e-mail addresses, port, host or """
# anything that should not change during the life of the server. return self._actionstart
# @actionstart.setter
# @param key the property name def actionstart(self, value):
# @param value the property value self._actionstart = value
self._logSys.debug("Set actionstart = %s" % value)
def setCInfo(self, key, value):
self.__cInfo[key] = value def start(self):
"""Executes the "actionstart" command.
## Replace the tags in the action command with actions properties
# Returns a "CInfo". and executes the resulting command.
# """
# @param key the property name if (self._properties and
not self.substituteRecursiveTags(self._properties)):
def getCInfo(self, key): self._logSys.error(
return self.__cInfo[key] "properties contain self referencing definitions "
"and cannot be resolved")
## raise RuntimeError("Error starting action")
# Removes a "CInfo". startCmd = self.replaceTag(self.actionstart, self._properties)
# if not self.executeCmd(startCmd, self.timeout):
# @param key the property name raise RuntimeError("Error starting action")
def delCInfo(self, key): @property
del self.__cInfo[key] def actionban(self):
"""The command used when a ban occurs.
## """
# Set the "start" command. return self._actionban
# @actionban.setter
# @param value the command def actionban(self, value):
self._actionban = value
def setActionStart(self, value): self._logSys.debug("Set actionban = %s" % value)
self.__actionStart = value
logSys.debug("Set actionStart = %s" % value) def ban(self, aInfo):
"""Executes the "actionban" command.
## Replace the tags in the action command with actions properties
# Get the "start" command. and ban information, and executes the resulting command.
# """
# @return the command if not self._processCmd(self.actionban, aInfo):
raise RuntimeError("Error banning %(ip)s" % aInfo)
def getActionStart(self):
return self.__actionStart @property
def actionunban(self):
## """The command used when an unban occurs.
# Executes the action "start" command. """
# return self._actionunban
# Replaces the tags in the action command with value of "cInfo" @actionunban.setter
# and executes the resulting command. def actionunban(self, value):
# self._actionunban = value
# @return True if the command succeeded self._logSys.debug("Set actionunban = %s" % value)
def execActionStart(self): def unban(self, aInfo):
if self.__cInfo: """Executes the "actionunban" command.
if not self.substituteRecursiveTags(self.__cInfo): Replace the tags in the action command with actions properties
logSys.error("Cinfo/definitions contain self referencing definitions and cannot be resolved") and ban information, and executes the resulting command.
return False """
startCmd = self.replaceTag(self.__actionStart, self.__cInfo) if not self._processCmd(self.actionunban, aInfo):
return self.executeCmd(startCmd, self.__timeout) raise RuntimeError("Error unbanning %(ip)s" % aInfo)
## @property
# Set the "ban" command. def actioncheck(self):
# """The command used to check correct environment in place for
# @param value the command ban action to take place.
"""
def setActionBan(self, value): return self._actioncheck
self.__actionBan = value @actioncheck.setter
logSys.debug("Set actionBan = %s" % value) def actioncheck(self, value):
self._actioncheck = value
## self._logSys.debug("Set actioncheck = %s" % value)
# Get the "ban" command.
# @property
# @return the command def actionstop(self):
"""The command executed when the jail/actions stops.
def getActionBan(self): """
return self.__actionBan return self._actionstop
@actionstop.setter
## def actionstop(self, value):
# Executes the action "ban" command. self._actionstop = value
# self._logSys.debug("Set actionstop = %s" % value)
# @return True if the command succeeded
def stop(self):
def execActionBan(self, aInfo): """Executes the "actionstop" command.
return self.__processCmd(self.__actionBan, aInfo) Replace the tags in the action command with actions properties
and executes the resulting command.
## """
# Set the "unban" command. stopCmd = self.replaceTag(self.actionstop, self._properties)
# if not self.executeCmd(stopCmd, self.timeout):
# @param value the command raise RuntimeError("Error stopping action")
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)
## ##
# Sort out tag definitions within other tags # Sort out tag definitions within other tags
@ -397,21 +346,21 @@ class CommandAction(ActionBase):
# @param aInfo Dynamic properties # @param aInfo Dynamic properties
# @return True if the command succeeded # @return True if the command succeeded
def __processCmd(self, cmd, aInfo = None): def _processCmd(self, cmd, aInfo = None):
""" Executes an OS command. """ Executes an OS command.
""" """
if cmd == "": if cmd == "":
logSys.debug("Nothing to do") self._logSys.debug("Nothing to do")
return True return True
checkCmd = self.replaceTag(self.__actionCheck, self.__cInfo) checkCmd = self.replaceTag(self.actioncheck, self._properties)
if not self.executeCmd(checkCmd, self.__timeout): if not self.executeCmd(checkCmd, self.timeout):
logSys.error("Invariant check failed. Trying to restore a sane" + self._logSys.error(
" environment") "Invariant check failed. Trying to restore a sane environment")
self.execActionStop() self.stop()
self.execActionStart() self.start()
if not self.executeCmd(checkCmd, self.__timeout): if not self.executeCmd(checkCmd, self.timeout):
logSys.fatal("Unable to restore environment") self._logSys.fatal("Unable to restore environment")
return False return False
# Replace tags # Replace tags
@ -421,9 +370,9 @@ class CommandAction(ActionBase):
realCmd = cmd realCmd = cmd
# Replace static fields # 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. # Executes a command.
@ -495,5 +444,6 @@ class CommandAction(ActionBase):
if msg: if msg:
logSys.info("HINT on %i: %s" logSys.info("HINT on %i: %s"
% (retcode, msg % locals())) % (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): def addAction(self, name, pythonModule=None, initOpts=None):
# Check is action name already exists # 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) raise ValueError("Action %s already exists" % name)
if pythonModule is None: if pythonModule is None:
action = CommandAction(name) action = CommandAction(self.jail, name)
else: else:
pythonModuleName = os.path.basename(pythonModule.strip(".py")) pythonModuleName = os.path.basename(pythonModule.strip(".py"))
customActionModule = imp.load_source( customActionModule = imp.load_source(
@ -91,7 +91,7 @@ class Actions(JailThread):
def delAction(self, name): def delAction(self, name):
for action in self.__actions: for action in self.__actions:
if action.getName() == name: if action.actionname == name:
self.__actions.remove(action) self.__actions.remove(action)
return return
raise KeyError("Invalid Action name: %s" % name) raise KeyError("Invalid Action name: %s" % name)
@ -106,7 +106,7 @@ class Actions(JailThread):
def getAction(self, name): def getAction(self, name):
for action in self.__actions: for action in self.__actions:
if action.getName() == name: if action.actionname == name:
return action return action
raise KeyError("Invalid Action name") raise KeyError("Invalid Action name")
@ -116,9 +116,7 @@ class Actions(JailThread):
# @return The last defined action. # @return The last defined action.
def getLastAction(self): def getLastAction(self):
action = self.__actions.pop() return self.__actions[-1]
self.__actions.append(action)
return action
## ##
# Returns the list of actions # Returns the list of actions
@ -169,10 +167,10 @@ class Actions(JailThread):
self.setActive(True) self.setActive(True)
for action in self.__actions: for action in self.__actions:
try: try:
action.execActionStart() action.start()
except Exception as e: except Exception as e:
logSys.error("Failed to start jail '%s' action '%s': %s", 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(): while self._isActive():
if not self.getIdle(): if not self.getIdle():
#logSys.debug(self.jail.getName() + ": action") #logSys.debug(self.jail.getName() + ": action")
@ -185,10 +183,10 @@ class Actions(JailThread):
self.__flushBan() self.__flushBan()
for action in self.__actions: for action in self.__actions:
try: try:
action.execActionStop() action.stop()
except Exception as e: except Exception as e:
logSys.error("Failed to stop jail '%s' action '%s': %s", 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") logSys.debug(self.jail.getName() + ": action terminated")
return True return True
@ -225,11 +223,11 @@ class Actions(JailThread):
logSys.warning("[%s] Ban %s" % (self.jail.getName(), aInfo["ip"])) logSys.warning("[%s] Ban %s" % (self.jail.getName(), aInfo["ip"]))
for action in self.__actions: for action in self.__actions:
try: try:
action.execActionBan(aInfo) action.ban(aInfo)
except Exception as e: except Exception as e:
logSys.error( logSys.error(
"Failed to execute ban jail '%s' action '%s': %s", "Failed to execute ban jail '%s' action '%s': %s",
self.jail.getName(), action.getName(), e) self.jail.getName(), action.actionname, e)
return True return True
else: else:
logSys.info("[%s] %s already banned" % (self.jail.getName(), 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"])) logSys.warning("[%s] Unban %s" % (self.jail.getName(), aInfo["ip"]))
for action in self.__actions: for action in self.__actions:
try: try:
action.execActionUnban(aInfo) action.unban(aInfo)
except Exception as e: except Exception as e:
logSys.error( logSys.error(
"Failed to execute unban jail '%s' action '%s': %s", "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): def delAction(self, name, value):
self.__jails.getAction(name).delAction(value) self.__jails.getAction(name).delAction(value)
def setCInfo(self, name, actionName, key, value): def getAction(self, name, value):
action = self.__jails.getAction(name).getAction(actionName) return self.__jails.getAction(name).getAction(value)
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 setBanTime(self, name, value): def setBanTime(self, name, value):
self.__jails.getAction(name).setBanTime(value) self.__jails.getAction(name).setBanTime(value)
@ -324,90 +306,6 @@ class Server:
def getBanTime(self, name): def getBanTime(self, name):
return self.__jails.getAction(name).getBanTime() 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 # Status
def status(self): def status(self):
try: try:

View File

@ -233,52 +233,22 @@ class Transmitter:
if len(command) > 3: if len(command) > 3:
args.extend([command[3], json.loads(command[4])]) args.extend([command[3], json.loads(command[4])])
self.__server.addAction(name, *args) self.__server.addAction(name, *args)
return self.__server.getLastAction(name).getName() return self.__server.getLastAction(name).actionname
elif command[1] == "delaction": elif command[1] == "delaction":
value = command[2] value = command[2]
self.__server.delAction(name, value) self.__server.delAction(name, value)
return None return None
elif command[1] == "setcinfo": elif command[1] == "action":
act = command[2] actionname = command[2]
key = command[3] actionkey = command[3]
value = " ".join(command[4:]) action = self.__server.getAction(name, actionname)
self.__server.setCInfo(name, act, key, value) if callable(getattr(action, actionkey, None)):
return self.__server.getCInfo(name, act, key) actionvalue = json.loads(command[4]) if len(command)>4 else {}
elif command[1] == "delcinfo": return getattr(action, actionkey)(**actionvalue)
act = command[2] else:
key = command[3] actionvalue = command[4]
self.__server.delCInfo(name, act, key) setattr(action, actionkey, actionvalue)
return None return getattr(action, actionkey)
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)
raise Exception("Invalid command (no set action or not yet implemented)") raise Exception("Invalid command (no set action or not yet implemented)")
def __commandGet(self, command): def __commandGet(self, command):
@ -330,31 +300,28 @@ class Transmitter:
elif command[1] == "bantime": elif command[1] == "bantime":
return self.__server.getBanTime(name) return self.__server.getBanTime(name)
elif command[1] == "actions": elif command[1] == "actions":
return self.__server.getActions(name) return [action.actionname
for action in self.__server.getActions(name)]
elif command[1] == "addaction": elif command[1] == "addaction":
return self.__server.getLastAction(name).getName() return self.__server.getLastAction(name).actionname
elif command[1] == "actionstart": elif command[1] == "action":
act = command[2] actionname = command[2]
return self.__server.getActionStart(name, act) actionvalue = command[3]
elif command[1] == "actionstop": action = self.__server.getAction(name, actionname)
act = command[2] return getattr(action, actionvalue)
return self.__server.getActionStop(name, act) elif command[1] == "actionproperties":
elif command[1] == "actioncheck": actionname = command[2]
act = command[2] action = self.__server.getAction(name, actionname)
return self.__server.getActionCheck(name, act) return [
elif command[1] == "actionban": key for key in dir(action)
act = command[2] if not key.startswith("_") and
return self.__server.getActionBan(name, act) not callable(getattr(action, key))]
elif command[1] == "actionunban": elif command[1] == "actionmethods":
act = command[2] actionname = command[2]
return self.__server.getActionUnban(name, act) action = self.__server.getAction(name, actionname)
elif command[1] == "cinfo": return [
act = command[2] key for key in dir(action)
key = command[3] if not key.startswith("_") and callable(getattr(action, key))]
return self.__server.getCInfo(name, act, key)
elif command[1] == "timeout":
act = command[2]
return self.__server.getActionTimeout(name, act)
raise Exception("Invalid command (no get action or not yet implemented)") raise Exception("Invalid command (no get action or not yet implemented)")
def status(self, command): def status(self, command):

View File

@ -49,11 +49,11 @@ class ExecuteActions(LogCaptureTestCase):
def defaultActions(self): def defaultActions(self):
self.__actions.addAction('ip') self.__actions.addAction('ip')
self.__ip = self.__actions.getAction('ip') self.__ip = self.__actions.getAction('ip')
self.__ip.setActionStart('echo ip start 64 >> "%s"' % self.__tmpfilename ) self.__ip.actionstart = 'echo ip start 64 >> "%s"' % self.__tmpfilename
self.__ip.setActionBan('echo ip ban <ip> >> "%s"' % self.__tmpfilename ) self.__ip.actionban = 'echo ip ban <ip> >> "%s"' % self.__tmpfilename
self.__ip.setActionUnban('echo ip unban <ip> >> "%s"' % self.__tmpfilename ) self.__ip.actionunban = 'echo ip unban <ip> >> "%s"' % self.__tmpfilename
self.__ip.setActionCheck('echo ip check <ip> >> "%s"' % self.__tmpfilename ) self.__ip.actioncheck = 'echo ip check <ip> >> "%s"' % self.__tmpfilename
self.__ip.setActionStop('echo ip stop >> "%s"' % self.__tmpfilename ) self.__ip.actionstop = 'echo ip stop >> "%s"' % self.__tmpfilename
def testActionsManipulation(self): def testActionsManipulation(self):
self.__actions.addAction('test') self.__actions.addAction('test')

View File

@ -31,17 +31,17 @@ from fail2ban.server.action import CommandAction, CallingMap
from fail2ban.tests.utils import LogCaptureTestCase from fail2ban.tests.utils import LogCaptureTestCase
class ExecuteAction(LogCaptureTestCase): class CommandActionTest(LogCaptureTestCase):
def setUp(self): def setUp(self):
"""Call before every test case.""" """Call before every test case."""
self.__action = CommandAction("Test") self.__action = CommandAction(None, "Test")
LogCaptureTestCase.setUp(self) LogCaptureTestCase.setUp(self)
def tearDown(self): def tearDown(self):
"""Call after every test case.""" """Call after every test case."""
LogCaptureTestCase.tearDown(self) LogCaptureTestCase.tearDown(self)
self.__action.execActionStop() self.__action.stop()
def testSubstituteRecursiveTags(self): def testSubstituteRecursiveTags(self):
aInfo = { aInfo = {
@ -105,62 +105,61 @@ class ExecuteAction(LogCaptureTestCase):
CallingMap(callme=lambda: int("a"))), "abc") CallingMap(callme=lambda: int("a"))), "abc")
def testExecuteActionBan(self): def testExecuteActionBan(self):
self.__action.setActionStart("touch /tmp/fail2ban.test") self.__action.actionstart = "touch /tmp/fail2ban.test"
self.assertEqual(self.__action.getActionStart(), "touch /tmp/fail2ban.test") self.assertEqual(self.__action.actionstart, "touch /tmp/fail2ban.test")
self.__action.setActionStop("rm -f /tmp/fail2ban.test") self.__action.actionstop = "rm -f /tmp/fail2ban.test"
self.assertEqual(self.__action.getActionStop(), 'rm -f /tmp/fail2ban.test') self.assertEqual(self.__action.actionstop, 'rm -f /tmp/fail2ban.test')
self.__action.setActionBan("echo -n") self.__action.actionban = "echo -n"
self.assertEqual(self.__action.getActionBan(), 'echo -n') self.assertEqual(self.__action.actionban, 'echo -n')
self.__action.setActionCheck("[ -e /tmp/fail2ban.test ]") self.__action.actioncheck = "[ -e /tmp/fail2ban.test ]"
self.assertEqual(self.__action.getActionCheck(), '[ -e /tmp/fail2ban.test ]') self.assertEqual(self.__action.actioncheck, '[ -e /tmp/fail2ban.test ]')
self.__action.setActionUnban("true") self.__action.actionunban = "true"
self.assertEqual(self.__action.getActionUnban(), 'true') self.assertEqual(self.__action.actionunban, 'true')
self.assertFalse(self._is_logged('returned')) self.assertFalse(self._is_logged('returned'))
# no action was actually executed yet # 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('Invariant check failed'))
self.assertTrue(self._is_logged('returned successfully')) self.assertTrue(self._is_logged('returned successfully'))
def testExecuteActionEmptyUnban(self): def testExecuteActionEmptyUnban(self):
self.__action.setActionUnban("") self.__action.actionunban = ""
self.assertTrue(self.__action.execActionUnban(None)) self.__action.unban({})
self.assertTrue(self._is_logged('Nothing to do')) self.assertTrue(self._is_logged('Nothing to do'))
def testExecuteActionStartCtags(self): def testExecuteActionStartCtags(self):
self.__action.setCInfo("HOST","192.0.2.0") self.__action.HOST = "192.0.2.0"
self.__action.setActionStart("touch /tmp/fail2ban.test.<HOST>") self.__action.actionstart = "touch /tmp/fail2ban.test.<HOST>"
self.__action.setActionStop("rm -f /tmp/fail2ban.test.<HOST>") self.__action.actionstop = "rm -f /tmp/fail2ban.test.<HOST>"
self.__action.setActionCheck("[ -e /tmp/fail2ban.test.192.0.2.0 ]") self.__action.actioncheck = "[ -e /tmp/fail2ban.test.192.0.2.0 ]"
self.assertTrue(self.__action.execActionStart()) self.__action.start()
def testExecuteActionCheckRestoreEnvironment(self): def testExecuteActionCheckRestoreEnvironment(self):
self.__action.setActionStart("") self.__action.actionstart = ""
self.__action.setActionStop("rm -f /tmp/fail2ban.test") self.__action.actionstop = "rm -f /tmp/fail2ban.test"
self.__action.setActionBan("rm /tmp/fail2ban.test") self.__action.actionban = "rm /tmp/fail2ban.test"
self.__action.setActionCheck("[ -e /tmp/fail2ban.test ]") self.__action.actioncheck = "[ -e /tmp/fail2ban.test ]"
self.assertFalse(self.__action.execActionBan(None)) self.assertRaises(RuntimeError, self.__action.ban, {'ip': None})
self.assertTrue(self._is_logged('Unable to restore environment')) self.assertTrue(self._is_logged('Unable to restore environment'))
def testExecuteActionChangeCtags(self): def testExecuteActionChangeCtags(self):
self.__action.setCInfo("ROST","192.0.2.0") self.assertRaises(AttributeError, getattr, self.__action, "ROST")
self.assertEqual(self.__action.getCInfo("ROST"),"192.0.2.0") self.__action.ROST = "192.0.2.0"
self.__action.delCInfo("ROST") self.assertEqual(self.__action.ROST,"192.0.2.0")
self.assertRaises(KeyError, self.__action.getCInfo, "ROST")
def testExecuteActionUnbanAinfo(self): def testExecuteActionUnbanAinfo(self):
aInfo = { aInfo = {
'ABC': "123", 'ABC': "123",
} }
self.__action.setActionBan("touch /tmp/fail2ban.test.123") self.__action.actionban = "touch /tmp/fail2ban.test.123"
self.__action.setActionUnban("rm /tmp/fail2ban.test.<ABC>") self.__action.actionunban = "rm /tmp/fail2ban.test.<ABC>"
self.assertTrue(self.__action.execActionBan(None)) self.__action.ban(aInfo)
self.assertTrue(self.__action.execActionUnban(aInfo)) self.__action.unban(aInfo)
def testExecuteActionStartEmpty(self): def testExecuteActionStartEmpty(self):
self.__action.setActionStart("") self.__action.actionstart = ""
self.assertTrue(self.__action.execActionStart()) self.__action.start()
self.assertTrue(self._is_logged('Nothing to do')) self.assertTrue(self._is_logged('Nothing to do'))
def testExecuteIncorrectCmd(self): def testExecuteIncorrectCmd(self):
@ -169,7 +168,9 @@ class ExecuteAction(LogCaptureTestCase):
def testExecuteTimeout(self): def testExecuteTimeout(self):
stime = time.time() 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.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 -- timed out after 2 seconds'))
self.assertTrue(self._is_logged('sleep 60 -- killed with SIGTERM')) 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', 'addaction', 'brokenaction'],
['set', ['set',
'brokenaction', 'brokenaction',
'actionban', 'action',
'brokenaction', 'brokenaction',
'actionban',
'hit with big stick <ip>'], 'hit with big stick <ip>'],
['set', 'brokenaction', 'actionstop', 'brokenaction', ''], ['set', 'brokenaction', 'action', 'brokenaction',
['set', 'brokenaction', 'actionstart', 'brokenaction', ''], 'actionstop', ''],
['set', 'brokenaction', 'actionunban', 'brokenaction', ''], ['set', 'brokenaction', 'action', 'brokenaction',
['set', 'brokenaction', 'actioncheck', 'brokenaction', ''], 'actionstart', ''],
['set', 'brokenaction', 'action', 'brokenaction',
'actionunban', ''],
['set', 'brokenaction', 'action', 'brokenaction',
'actioncheck', ''],
['add', 'parse_to_end_of_jail.conf', 'auto'], ['add', 'parse_to_end_of_jail.conf', 'auto'],
['set', 'parse_to_end_of_jail.conf', 'usedns', 'warn'], ['set', 'parse_to_end_of_jail.conf', 'usedns', 'warn'],
['set', 'parse_to_end_of_jail.conf', 'maxretry', 3], ['set', 'parse_to_end_of_jail.conf', 'maxretry', 3],
@ -486,7 +491,7 @@ class JailsReaderTest(LogCaptureTestCase):
self.assertTrue('blocktype' in action._initOpts) self.assertTrue('blocktype' in action._initOpts)
# Verify that we have a call to set it up # Verify that we have a call to set it up
blocktype_present = False 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: for command in commands:
if (len(command) > 5 and if (len(command) > 5 and
command[:5] == target_command): command[:5] == target_command):

View File

@ -3,20 +3,26 @@ from fail2ban.server.action import ActionBase
class TestAction(ActionBase): class TestAction(ActionBase):
def __init__(self, jail, name, opt1, opt2=None): def __init__(self, jail, actionname, opt1, opt2=None):
super(TestAction, self).__init__(jail, name) super(TestAction, self).__init__(jail, actionname)
self.logSys.debug("%s initialised" % self.__class__.__name__) self._logSys.debug("%s initialised" % self.__class__.__name__)
self.opt1 = opt1
self.opt2 = opt2
self._opt3 = "Hello"
def execActionStart(self): def start(self):
self.logSys.debug("%s action start" % self.__class__.__name__) self._logSys.debug("%s action start" % self.__class__.__name__)
def execActionStop(self): def stop(self):
self.logSys.debug("%s action stop" % self.__class__.__name__) self._logSys.debug("%s action stop" % self.__class__.__name__)
def execActionBan(self, aInfo): def ban(self, aInfo):
self.logSys.debug("%s action ban" % self.__class__.__name__) self._logSys.debug("%s action ban" % self.__class__.__name__)
def execActionUnban(self, aInfo): def unban(self, aInfo):
self.logSys.debug("%s action unban" % self.__class__.__name__) self._logSys.debug("%s action unban" % self.__class__.__name__)
def testmethod(self, text):
return "%s %s %s" % (self._opt3, text, self.opt1)
Action = TestAction Action = TestAction

View File

@ -523,40 +523,40 @@ class Transmitter(TransmitterBase):
(0, action)) (0, action))
self.assertEqual( self.assertEqual(
self.transm.proceed( self.transm.proceed(
["get", self.jailName, "actions"])[1][0].getName(), ["get", self.jailName, "actions"])[1][0],
action) action)
for cmd, value in zip(cmdList, cmdValueList): for cmd, value in zip(cmdList, cmdValueList):
self.assertEqual( self.assertEqual(
self.transm.proceed( self.transm.proceed(
["set", self.jailName, cmd, action, value]), ["set", self.jailName, "action", action, cmd, value]),
(0, value)) (0, value))
for cmd, value in zip(cmdList, cmdValueList): for cmd, value in zip(cmdList, cmdValueList):
self.assertEqual( self.assertEqual(
self.transm.proceed(["get", self.jailName, cmd, action]), self.transm.proceed(["get", self.jailName, "action", action, cmd]),
(0, value)) (0, value))
self.assertEqual( self.assertEqual(
self.transm.proceed( self.transm.proceed(
["set", self.jailName, "setcinfo", action, "KEY", "VALUE"]), ["set", self.jailName, "action", action, "KEY", "VALUE"]),
(0, "VALUE")) (0, "VALUE"))
self.assertEqual( self.assertEqual(
self.transm.proceed( self.transm.proceed(
["get", self.jailName, "cinfo", action, "KEY"]), ["get", self.jailName, "action", action, "KEY"]),
(0, "VALUE")) (0, "VALUE"))
self.assertEqual( self.assertEqual(
self.transm.proceed( self.transm.proceed(
["get", self.jailName, "cinfo", action, "InvalidKey"])[0], ["get", self.jailName, "action", action, "InvalidKey"])[0],
1) 1)
self.assertEqual( self.assertEqual(
self.transm.proceed( self.transm.proceed(
["set", self.jailName, "delcinfo", action, "KEY"]), ["get", self.jailName, "action", action, "actionname"]),
(0, None)) (0, action))
self.assertEqual( self.assertEqual(
self.transm.proceed( self.transm.proceed(
["set", self.jailName, "timeout", action, "10"]), ["set", self.jailName, "action", action, "timeout", "10"]),
(0, 10)) (0, 10))
self.assertEqual( self.assertEqual(
self.transm.proceed( self.transm.proceed(
["get", self.jailName, "timeout", action]), ["get", self.jailName, "action", action, "timeout"]),
(0, 10)) (0, 10))
self.assertEqual( self.assertEqual(
self.transm.proceed(["set", self.jailName, "delaction", action]), self.transm.proceed(["set", self.jailName, "delaction", action]),
@ -564,23 +564,42 @@ class Transmitter(TransmitterBase):
self.assertEqual( self.assertEqual(
self.transm.proceed( self.transm.proceed(
["set", self.jailName, "delaction", "Doesn't exist"])[0],1) ["set", self.jailName, "delaction", "Doesn't exist"])[0],1)
def testPythonActionMethodsAndProperties(self):
action = "TestCaseAction"
self.assertEqual( self.assertEqual(
self.transm.proceed(["set", self.jailName, "addaction", action, self.transm.proceed(["set", self.jailName, "addaction", action,
os.path.join(TEST_FILES_DIR, "action.d", "action.py"), os.path.join(TEST_FILES_DIR, "action.d", "action.py"),
'{"opt1": "value"}']), '{"opt1": "value"}']),
(0, action)) (0, action))
for cmd, value in zip(cmdList, cmdValueList): self.assertEqual(
self.assertTrue( sorted(self.transm.proceed(["get", self.jailName,
isinstance(self.transm.proceed( "actionproperties", action])),
["set", self.jailName, cmd, action, value])[1], [0, ['actionname', 'opt1', 'opt2']])
TypeError), self.assertEqual(
"set %s for python action did not raise TypeError" % cmd) self.transm.proceed(["get", self.jailName, "action", action,
for cmd, value in zip(cmdList, cmdValueList): "opt1"]),
self.assertTrue( (0, 'value'))
isinstance(self.transm.proceed( self.assertEqual(
["get", self.jailName, cmd, action])[1], self.transm.proceed(["get", self.jailName, "action", action,
TypeError), "opt2"]),
"get %s for python action did not raise TypeError" % cmd) (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): def testNOK(self):
self.assertEqual(self.transm.proceed(["INVALID", "COMMAND"])[0],1) 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.Transmitter))
tests.addTest(unittest.makeSuite(servertestcase.JailTests)) tests.addTest(unittest.makeSuite(servertestcase.JailTests))
tests.addTest(unittest.makeSuite(servertestcase.RegexTests)) tests.addTest(unittest.makeSuite(servertestcase.RegexTests))
tests.addTest(unittest.makeSuite(actiontestcase.ExecuteAction)) tests.addTest(unittest.makeSuite(actiontestcase.CommandActionTest))
tests.addTest(unittest.makeSuite(actionstestcase.ExecuteActions)) tests.addTest(unittest.makeSuite(actionstestcase.ExecuteActions))
# FailManager # FailManager
tests.addTest(unittest.makeSuite(failmanagertestcase.AddFailure)) 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 will try to use the following backends, in order: pyinotify, gamin, polling
.PP .PP
.SS Actions .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 .PP
.nf .nf
[ssh-iptables-ipset] [ssh-iptables-ipset]
@ -163,7 +163,7 @@ two commands to be executed.
actionban = iptables -I fail2ban-<name> --source <ip> -j DROP actionban = iptables -I fail2ban-<name> --source <ip> -j DROP
echo ip=<ip>, match=<match>, time=<time> >> /var/log/fail2ban.log echo ip=<ip>, match=<match>, time=<time> >> /var/log/fail2ban.log
.TP .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" .SS "Action Tags"
The following tags are substituted in the actionban, actionunban and actioncheck (when called before actionban/actionunban) commands. 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") glob("config/filter.d/*.conf")
), ),
('/etc/fail2ban/action.d', ('/etc/fail2ban/action.d',
glob("config/action.d/*.*") glob("config/action.d/*.conf") +
glob("config/action.d/*.py")
), ),
('/etc/fail2ban/fail2ban.d', ('/etc/fail2ban/fail2ban.d',
'' ''