Merge pull request #549 from kwirk/python-actions

ENH: Python actions
pull/568/merge
Daniel Black 2014-01-06 02:58:45 -08:00
commit ab3ded2205
47 changed files with 1513 additions and 1015 deletions

225
config/action.d/smtp.py Normal file
View File

@ -0,0 +1,225 @@
# 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
import smtplib
from email.mime.text import MIMEText
from email.utils import formatdate, formataddr
from fail2ban.server.actions import ActionBase, CallingMap
messages = {}
messages['start'] = \
"""Hi,
The jail %(jailname)s has been started successfully.
Regards,
Fail2Ban"""
messages['stop'] = \
"""Hi,
The jail %(jailname)s has been stopped.
Regards,
Fail2Ban"""
messages['ban'] = {}
messages['ban']['head'] = \
"""Hi,
The IP %(ip)s has just been banned for %(bantime)s seconds
by Fail2Ban after %(failures)i attempts against %(jailname)s.
"""
messages['ban']['tail'] = \
"""
Regards,
Fail2Ban"""
messages['ban']['matches'] = \
"""
Matches for this ban:
%(matches)s
"""
messages['ban']['ipmatches'] = \
"""
Matches for %(ip)s:
%(ipmatches)s
"""
messages['ban']['ipjailmatches'] = \
"""
Matches for %(ip)s for jail %(jailname)s:
%(ipjailmatches)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):
"""Initialise action.
Parameters
----------
jail : Jail
The jail which the action belongs to.
name : str
Named assigned to the action.
host : str, optional
SMTP host, of host:port format. Default host "localhost" and
port "25"
user : str, optional
Username used for authentication with SMTP server.
password : str, optional
Password used for authentication with SMTP server.
sendername : str, optional
Name to use for from address in email. Default "Fail2Ban".
sender : str, optional
Email address to use for from address in email.
Default "fail2ban".
dest : str, optional
Email addresses of intended recipient(s) in comma delimited
format. Default "root".
matches : str, optional
Type of matches to be included from ban in email. Can be one
of "matches", "ipmatches" or "ipjailmatches". Default None
(see man jail.conf.5).
"""
super(SMTPAction, self).__init__(jail, name)
self.host = host
#TODO: self.ssl = ssl
self.user = user
self.password =password
self.fromname = sendername
self.fromaddr = sender
self.toaddr = dest
self.matches = matches
self.message_values = CallingMap(
jailname = self._jail.getName(), # Doesn't change
hostname = socket.gethostname,
bantime = self._jail.actions.getBanTime,
)
def _sendMessage(self, subject, text):
"""Sends message based on arguments and instance's properties.
Parameters
----------
subject : str
Subject of the email.
text : str
Body of the email.
Raises
------
SMTPConnectionError
Error on connecting to host.
SMTPAuthenticationError
Error authenticating with SMTP server.
SMTPException
See Python `smtplib` for full list of other possible
exceptions.
"""
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
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 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 ban(self, aInfo):
"""Sends email to recipients informing that ban has occurred.
Parameters
----------
aInfo : dict
Dictionary which includes information in relation to
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

@ -25,7 +25,8 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import logging, os
from configreader import ConfigReader, DefinitionInitConfigReader
from .configreader import ConfigReader, DefinitionInitConfigReader
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)
@ -58,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

@ -23,7 +23,7 @@ __license__ = "GPL"
import logging
from fail2ban.exceptions import UnknownJailException, DuplicateJailException
from ..exceptions import UnknownJailException, DuplicateJailException
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)
@ -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

@ -25,9 +25,10 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import glob, logging, os
from configparserinc import SafeConfigParserWithIncludes
from ConfigParser import NoOptionError, NoSectionError
from .configparserinc import SafeConfigParserWithIncludes
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)

View File

@ -25,9 +25,10 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import logging
from configreader import ConfigReader
from fail2banreader import Fail2banReader
from jailsreader import JailsReader
from .configreader import ConfigReader
from .fail2banreader import Fail2banReader
from .jailsreader import JailsReader
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)

View File

@ -25,7 +25,8 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import logging
from configreader import ConfigReader
from .configreader import ConfigReader
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)

View File

@ -25,7 +25,8 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import logging, os, shlex
from configreader import ConfigReader, DefinitionInitConfigReader
from .configreader import ConfigReader, DefinitionInitConfigReader
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)

View File

@ -25,10 +25,11 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import logging, re, glob, os.path
import json
from configreader import ConfigReader
from filterreader import FilterReader
from actionreader import ActionReader
from .configreader import ConfigReader
from .filterreader import FilterReader
from .actionreader import ActionReader
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)
@ -120,14 +121,26 @@ class JailReader(ConfigReader):
if not act: # skip empty actions
continue
actName, actOpt = JailReader.extractOptions(act)
action = ActionReader(
actName, self.__name, actOpt, basedir=self.getBaseDir())
ret = action.read()
if ret:
action.getOptions(self.__opts)
self.__actions.append(action)
if actName.endswith(".py"):
self.__actions.append([
"set",
self.__name,
"addaction",
actOpt.pop("actname", os.path.splitext(actName)[0]),
os.path.join(
self.getBaseDir(), "action.d", actName),
json.dumps(actOpt),
])
else:
raise AttributeError("Unable to read action")
action = ActionReader(
actName, self.__name, actOpt,
basedir=self.getBaseDir())
ret = action.read()
if ret:
action.getOptions(self.__opts)
self.__actions.append(action)
else:
raise AttributeError("Unable to read action")
except Exception, e:
logSys.error("Error in action definition " + act)
logSys.debug("Caught exception: %s" % (e,))
@ -193,7 +206,10 @@ class JailReader(ConfigReader):
if self.__filter:
stream.extend(self.__filter.convert())
for action in self.__actions:
stream.extend(action.convert())
if isinstance(action, ConfigReader):
stream.extend(action.convert())
else:
stream.append(action)
stream.insert(0, ["add", self.__name, backend])
return stream

View File

@ -25,8 +25,9 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import logging
from configreader import ConfigReader
from jailreader import JailReader
from .configreader import ConfigReader
from .jailreader import JailReader
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)

View File

@ -29,7 +29,7 @@ __license__ = "GPL"
class DuplicateJailException(Exception):
pass
class UnknownJailException(Exception):
class UnknownJailException(KeyError):
pass

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>", "adds a new action named <NAME> for <JAIL>"],
["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>"],
@ -100,15 +102,18 @@ protocol = [
["get <JAIL> usedns", "gets the usedns setting for <JAIL>"],
["get <JAIL> maxretry", "gets the number of failures allowed for <JAIL>"],
["get <JAIL> maxlines", "gets the number of lines to buffer for <JAIL>"],
["get <JAIL> 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,12 +130,14 @@ def printFormatted():
print
firstHeading = True
first = True
for n in textwrap.wrap(m[1], WIDTH):
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
line = ' ' * (INDENT + MARGIN) + n.strip()
print line
##

View File

@ -23,6 +23,8 @@ __license__ = "GPL"
import logging, os, subprocess, time, signal, tempfile
import threading, re
from abc import ABCMeta
from collections import MutableMapping
#from subprocess import call
# Gets the instance of the logger.
@ -34,7 +36,7 @@ _cmd_lock = threading.Lock()
# Some hints on common abnormal exit codes
_RETCODE_HINTS = {
127: '"Command not found". Make sure that all commands in %(realCmd)r '
'are in the PATH of fail2ban-server process '
'are in the PATH of fail2ban-server process '
'(grep -a PATH= /proc/`pidof -x fail2ban-server`/environ). '
'You may want to start '
'"fail2ban-server -f" separately, initiate it with '
@ -46,232 +48,326 @@ _RETCODE_HINTS = {
signame = dict((num, name)
for name, num in signal.__dict__.iteritems() if name.startswith("SIG"))
##
# 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 CallingMap(MutableMapping):
"""A Mapping type which returns the result of callable values.
class Action:
def __init__(self, name):
self.__name = name
self.__timeout = 60
self.__cInfo = dict()
## Command executed in order to initialize the system.
self.__actionStart = ''
## Command executed when an IP address gets banned.
self.__actionBan = ''
## Command executed when an IP address gets removed.
self.__actionUnban = ''
## Command executed in order to check requirements.
self.__actionCheck = ''
## Command executed in order to stop the system.
self.__actionStop = ''
logSys.debug("Created Action")
##
# Sets the action name.
#
# @param name the name of the action
def setName(self, name):
self.__name = name
##
# Returns the action name.
#
# @return the name of the action
def getName(self):
return self.__name
##
# 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.__name, 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 Action.substituteRecursiveTags(self.__cInfo):
logSys.error("Cinfo/definitions contain self referencing definitions and cannot be resolved")
`CallingMap` behaves similar to a standard python dictionary,
with the exception that any values which are callable, are called
and the result is returned as the value.
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.
Attributes
----------
data : dict
The dictionary data which can be accessed to obtain items
without callable values being called.
"""
def __init__(self, *args, **kwargs):
self.data = dict(*args, **kwargs)
def __getitem__(self, key):
value = self.data[key]
if callable(value):
return value()
else:
return value
def __setitem__(self, key, value):
self.data[key] = value
def __delitem__(self, key):
del self.data[key]
def __iter__(self):
return iter(self.data)
def __len__(self):
return len(self.data)
class ActionBase(object):
"""An abstract base class for actions in Fail2Ban.
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.
Required methods:
- __init__(jail, name)
- start()
- stop()
- ban(aInfo)
- unban(aInfo)
"""
__metaclass__ = ABCMeta
@classmethod
def __subclasshook__(cls, C):
required = (
"start",
"stop",
"ban",
"unban",
)
for method in required:
if not callable(getattr(C, method, None)):
return False
startCmd = Action.replaceTag(self.__actionStart, self.__cInfo)
return Action.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 = Action.replaceTag(self.__actionStop, self.__cInfo)
return Action.executeCmd(stopCmd, self.__timeout)
return True
##
# Sort out tag definitions within other tags
#
# so: becomes:
# a = 3 a = 3
# b = <a>_3 b = 3_3
# @param tags, a dictionary
# @returns tags altered or False if there is a recursive definition
#@staticmethod
def __init__(self, jail, name):
"""Initialise action.
Called when action is created, but before the jail/actions is
started. This should carry out necessary methods to initialise
the action but not "start" the action.
Parameters
----------
jail : Jail
The jail in which the action belongs to.
name : str
Name assigned to the action.
Notes
-----
Any additional arguments specified in `jail.conf` or passed
via `fail2ban-client` will be passed as keyword arguments.
"""
self._jail = jail
self._name = name
self._logSys = logging.getLogger(
'%s.%s' % (__name__, self.__class__.__name__))
def start(self):
"""Executed when the jail/action is started.
"""
pass
def stop(self):
"""Executed when the jail/action is stopped.
"""
pass
def ban(self, aInfo):
"""Executed when a ban occurs.
Parameters
----------
aInfo : dict
Dictionary which includes information in relation to
the ban.
"""
pass
def unban(self, aInfo):
"""Executed when a ban expires.
Parameters
----------
aInfo : dict
Dictionary which includes information in relation to
the ban.
"""
pass
class CommandAction(ActionBase):
"""A action which executes OS shell commands.
This is the default type of action which Fail2Ban uses.
"""
def __init__(self, jail, name):
"""Initialise action.
Default sets all commands for actions as empty string, such
no command is executed.
Parameters
----------
jail : Jail
The jail in which the action belongs to.
name : str
Name assigned to the action.
"""
super(CommandAction, self).__init__(jail, name)
self.timeout = 60
## Command executed in order to initialize the system.
self.actionstart = ''
## Command executed when an IP address gets banned.
self.actionban = ''
## Command executed when an IP address gets removed.
self.actionunban = ''
## Command executed in order to check requirements.
self.actioncheck = ''
## Command executed in order to stop the system.
self.actionstop = ''
self._logSys.debug("Created %s" % self.__class__)
@classmethod
def __subclasshook__(cls, C):
return NotImplemented # Standard checks
@property
def timeout(self):
"""Time out 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._name, self.timeout))
@property
def _properties(self):
"""A dictionary of the actions properties.
This is used to subsitute "tags" in the commands.
"""
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.
Replaces the tags in the action command with actions properties
and ban information, and executes the resulting command.
Parameters
----------
aInfo : dict
Dictionary which includes information in relation to
the ban.
"""
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.
Replaces the tags in the action command with actions properties
and ban information, and executes the resulting command.
Parameters
----------
aInfo : dict
Dictionary which includes information in relation to
the ban.
"""
if not self._processCmd(self.actionunban, aInfo):
raise RuntimeError("Error unbanning %(ip)s" % aInfo)
@property
def actioncheck(self):
"""The command used to check the environment.
This is used prior to a ban taking place to ensure the
environment is appropriate. If this check fails, `stop` and
`start` is executed prior to the check being called again.
"""
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.
Replaces 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")
@staticmethod
def substituteRecursiveTags(tags):
"""Sort out tag definitions within other tags.
so: becomes:
a = 3 a = 3
b = <a>_3 b = 3_3
Parameters
----------
tags : dict
Dictionary of tags(keys) and their values.
Returns
-------
dict
Dictionary of tags(keys) and their values, with tags
within the values recursively replaced.
"""
t = re.compile(r'<([^ >]+)>')
for tag, value in tags.iteritems():
value = str(value)
@ -291,98 +387,130 @@ class Action:
m = t.search(value, m.start() + 1)
tags[tag] = value
return tags
substituteRecursiveTags = staticmethod(substituteRecursiveTags)
#@staticmethod
def escapeTag(tag):
@staticmethod
def escapeTag(value):
"""Escape characters which may be used for command injection.
Parameters
----------
value : str
A string of which characters will be escaped.
Returns
-------
str
`value` with certain characters escaped.
Notes
-----
The following characters are escaped::
\\#&;`|*?~<>^()[]{}$'"
"""
for c in '\\#&;`|*?~<>^()[]{}$\'"':
if c in tag:
tag = tag.replace(c, '\\' + c)
return tag
escapeTag = staticmethod(escapeTag)
if c in value:
value = value.replace(c, '\\' + c)
return value
##
# Replaces tags in query with property values in aInfo.
#
# @param query the query string with tags
# @param aInfo the properties
# @return a string
#@staticmethod
def replaceTag(query, aInfo):
""" Replace tags in query
@classmethod
def replaceTag(cls, query, aInfo):
"""Replaces tags in `query` with property values.
Parameters
----------
query : str
String with tags.
aInfo : dict
Tags(keys) and associated values for substitution in query.
Returns
-------
str
`query` string with tags replaced.
"""
string = query
for tag, value in aInfo.iteritems():
for tag in aInfo:
if "<%s>" % tag in query:
if callable(value):
value = value()
value = str(value) # assure string
value = str(aInfo[tag]) # assure string
if tag.endswith('matches'):
# That one needs to be escaped since its content is
# out of our control
value = Action.escapeTag(value)
value = cls.escapeTag(value)
string = string.replace('<' + tag + '>', value)
# New line
string = string.replace("<br>", '\n')
return string
replaceTag = staticmethod(replaceTag)
##
# Executes a command with preliminary checks and substitutions.
#
# Before executing any commands, executes the "check" command first
# in order to check if pre-requirements are met. If this check fails,
# it tries to restore a sane environment before executing the real
# command.
# Replaces "aInfo" and "cInfo" in the query too.
#
# @param cmd The command to execute
# @param aInfo Dynamic properties
# @return True if the command succeeded
def __processCmd(self, cmd, aInfo = None):
""" Executes an OS command.
def _processCmd(self, cmd, aInfo = None):
"""Executes a command with preliminary checks and substitutions.
Before executing any commands, executes the "check" command first
in order to check if pre-requirements are met. If this check fails,
it tries to restore a sane environment before executing the real
command.
Parameters
----------
cmd : str
The command to execute.
aInfo : dictionary
Dynamic properties.
Returns
-------
bool
True if the command succeeded.
"""
if cmd == "":
logSys.debug("Nothing to do")
self._logSys.debug("Nothing to do")
return True
checkCmd = Action.replaceTag(self.__actionCheck, self.__cInfo)
if not Action.executeCmd(checkCmd, self.__timeout):
logSys.error("Invariant check failed. Trying to restore a sane" +
" environment")
self.execActionStop()
self.execActionStart()
if not Action.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
if not aInfo is None:
realCmd = Action.replaceTag(cmd, aInfo)
realCmd = self.replaceTag(cmd, aInfo)
else:
realCmd = cmd
# Replace static fields
realCmd = Action.replaceTag(realCmd, self.__cInfo)
realCmd = self.replaceTag(realCmd, self._properties)
return Action.executeCmd(realCmd, self.__timeout)
return self.executeCmd(realCmd, self.timeout)
##
# Executes a command.
#
# We need a shell here because commands are mainly shell script. They
# contain pipe, redirection, etc.
#
# @todo Force the use of bash!?
# @todo Kill the command after a given timeout
#
# @param realCmd the command to execute
# @return True if the command succeeded
#@staticmethod
@staticmethod
def executeCmd(realCmd, timeout=60):
"""Executes a command.
Parameters
----------
realCmd : str
The command to execute.
timeout : int
The time out in seconds for the command.
Returns
-------
bool
True if the command succeeded.
Raises
------
OSError
If command fails to be executed.
RuntimeError
If command execution times out.
"""
logSys.debug(realCmd)
if not realCmd:
logSys.debug("Nothing to do")
@ -412,7 +540,6 @@ class Action:
retcode = popen.poll()
except OSError, e:
logSys.error("%s -- failed with %s" % (realCmd, e))
return False
finally:
_cmd_lock.release()
@ -439,6 +566,6 @@ class Action:
if msg:
logSys.info("HINT on %i: %s"
% (retcode, msg % locals()))
return False
executeCmd = staticmethod(executeCmd)
return False
raise RuntimeError("Command execution failed: %s" % realCmd)

View File

@ -24,94 +24,113 @@ __author__ = "Cyril Jaquier"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
from banmanager import BanManager
from jailthread import JailThread
from action import Action
from mytime import MyTime
import time, logging
import os
import imp
from collections import Mapping
from .banmanager import BanManager
from .jailthread import JailThread
from .action import ActionBase, CommandAction, CallingMap
from .mytime import MyTime
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)
##
# 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 Actions(JailThread, Mapping):
"""Handles jail actions.
This class handles the actions of the jail. Creation, deletion or to
actions must be done through this class. This class is based on the
Mapping type, and the `add` method must be used to add new actions.
This class also starts and stops the actions, and fetches bans from
the jail executing these bans via the actions.
"""
class Actions(JailThread):
##
# Constructor.
#
# Initialize the filter object with default values.
# @param jail the jail object
def __init__(self, jail):
"""Initialise an empty Actions instance.
Parameters
----------
jail: Jail
The jail of which the actions belongs to.
"""
JailThread.__init__(self)
## The jail which contains this action.
self.jail = jail
self.__actions = list()
self._jail = jail
self._actions = dict()
## The ban manager.
self.__banManager = BanManager()
##
# Adds an action.
#
# @param name The action name
def addAction(self, name):
def add(self, name, pythonModule=None, initOpts=None):
"""Adds a new action.
Add a new action if not already present, defaulting to standard
`CommandAction`, or specified Python module.
Parameters
----------
name : str
The name of the action.
pythonModule : str, optional
Path to Python file which must contain `Action` class.
Default None, which means `CommandAction` is used.
initOpts : dict, optional
Options for Python Action, used as keyword arguments for
initialisation. Default None.
Raises
------
ValueError
If action name already exists.
RuntimeError
If external Python module does not have `Action` class
or does not implement necessary methods as per `ActionBase`
abstract class.
"""
# Check is action name already exists
if name in [action.getName() for action in self.__actions]:
if name in self._actions:
raise ValueError("Action %s already exists" % name)
action = Action(name)
self.__actions.append(action)
##
# Removes an action.
#
# @param name The action name
def delAction(self, name):
for action in self.__actions:
if action.getName() == name:
self.__actions.remove(action)
return
raise KeyError("Invalid Action name: %s" % name)
##
# Returns an action.
#
# Raises a KeyError exception if the action does not exist.
#
# @param name the action name
# @return the action
def getAction(self, name):
for action in self.__actions:
if action.getName() == name:
return action
raise KeyError("Invalid Action name")
##
# Returns the last defined action.
#
# @return The last defined action.
def getLastAction(self):
action = self.__actions.pop()
self.__actions.append(action)
return action
##
# Returns the list of actions
#
# @return list of actions
def getActions(self):
return self.__actions
if pythonModule is None:
action = CommandAction(self._jail, name)
else:
pythonModuleName = os.path.basename(pythonModule.strip(".py"))
customActionModule = imp.load_source(
pythonModuleName, pythonModule)
if not hasattr(customActionModule, "Action"):
raise RuntimeError(
"%s module does not have 'Action' class" % pythonModule)
elif not issubclass(customActionModule.Action, ActionBase):
raise RuntimeError(
"%s module %s does not implement required methods" % (
pythonModule, customActionModule.Action.__name__))
action = customActionModule.Action(self._jail, name, **initOpts)
self._actions[name] = action
def __getitem__(self, name):
try:
return self._actions[name]
except KeyError:
raise KeyError("Invalid Action name: %s" % name)
def __delitem__(self, name):
try:
del self._actions[name]
except KeyError:
raise KeyError("Invalid Action name: %s" % name)
def __iter__(self):
return iter(self._actions)
def __len__(self):
return len(self._actions)
def __eq__(self, other): # Required for Threading
return False
def __hash__(self): # Required for Threading
return id(self)
##
# Set the ban time.
#
@ -128,34 +147,52 @@ class Actions(JailThread):
def getBanTime(self):
return self.__banManager.getBanTime()
##
# Remove a banned IP now, rather than waiting for it to expire, even if set to never expire.
#
# @return the IP string or 'None' if not unbanned.
def removeBannedIP(self, ip):
"""Removes banned IP calling actions' unban method
Remove a banned IP now, rather than waiting for it to expire,
even if set to never expire.
Parameters
----------
ip : str
The IP address to unban
Raises
------
ValueError
If `ip` is not banned
"""
# Find the ticket with the IP.
ticket = self.__banManager.getTicketByIP(ip)
if ticket is not None:
# Unban the IP.
self.__unBan(ticket)
return ip
raise ValueError("IP %s is not banned" % ip)
else:
raise ValueError("IP %s is not banned" % ip)
##
# Main loop.
#
# This function is the main loop of the thread. It checks the Jail
# queue and executes commands when an IP address is banned.
# @return True when the thread exits nicely
def run(self):
"""Main loop for Threading.
This function is the main loop of the thread. It checks the jail
queue and executes commands when an IP address is banned.
Returns
-------
bool
True when the thread exits nicely.
"""
self.setActive(True)
for action in self.__actions:
action.execActionStart()
for name, action in self._actions.iteritems():
try:
action.start()
except Exception as e:
logSys.error("Failed to start jail '%s' action '%s': %s",
self._jail.getName(), name, e)
while self._isActive():
if not self.getIdle():
#logSys.debug(self.jail.getName() + ": action")
#logSys.debug(self._jail.getName() + ": action")
ret = self.__checkBan()
if not ret:
self.__checkUnBan()
@ -163,94 +200,116 @@ class Actions(JailThread):
else:
time.sleep(self.getSleepTime())
self.__flushBan()
for action in self.__actions:
action.execActionStop()
logSys.debug(self.jail.getName() + ": action terminated")
for name, action in self._actions.iteritems():
try:
action.stop()
except Exception as e:
logSys.error("Failed to stop jail '%s' action '%s': %s",
self._jail.getName(), name, e)
logSys.debug(self._jail.getName() + ": action terminated")
return True
##
# Check for IP address to ban.
#
# Look in the Jail queue for FailTicket. If a ticket is available,
# it executes the "ban" command and add a ticket to the BanManager.
# @return True if an IP address get banned
def __checkBan(self):
ticket = self.jail.getFailTicket()
"""Check for IP address to ban.
Look in the jail queue for FailTicket. If a ticket is available,
it executes the "ban" command and adds a ticket to the BanManager.
Returns
-------
bool
True if an IP address get banned.
"""
ticket = self._jail.getFailTicket()
if ticket != False:
aInfo = dict()
aInfo = CallingMap()
bTicket = BanManager.createBanTicket(ticket)
aInfo["ip"] = bTicket.getIP()
aInfo["failures"] = bTicket.getAttempt()
aInfo["time"] = bTicket.getTime()
aInfo["matches"] = "\n".join(bTicket.getMatches())
if self.jail.getDatabase() is not None:
if self._jail.getDatabase() is not None:
aInfo["ipmatches"] = lambda: "\n".join(
self.jail.getDatabase().getBansMerged(
self._jail.getDatabase().getBansMerged(
ip=bTicket.getIP()).getMatches())
aInfo["ipjailmatches"] = lambda: "\n".join(
self.jail.getDatabase().getBansMerged(
ip=bTicket.getIP(), jail=self.jail).getMatches())
self._jail.getDatabase().getBansMerged(
ip=bTicket.getIP(), jail=self._jail).getMatches())
aInfo["ipfailures"] = lambda: "\n".join(
self.jail.getDatabase().getBansMerged(
self._jail.getDatabase().getBansMerged(
ip=bTicket.getIP()).getAttempt())
aInfo["ipjailfailures"] = lambda: "\n".join(
self.jail.getDatabase().getBansMerged(
ip=bTicket.getIP(), jail=self.jail).getAttempt())
self._jail.getDatabase().getBansMerged(
ip=bTicket.getIP(), jail=self._jail).getAttempt())
if self.__banManager.addBanTicket(bTicket):
logSys.warning("[%s] Ban %s" % (self.jail.getName(), aInfo["ip"]))
for action in self.__actions:
action.execActionBan(aInfo)
logSys.warning("[%s] Ban %s" % (self._jail.getName(), aInfo["ip"]))
for name, action in self._actions.iteritems():
try:
action.ban(aInfo)
except Exception as e:
logSys.error(
"Failed to execute ban jail '%s' action '%s': %s",
self._jail.getName(), name, e)
return True
else:
logSys.info("[%s] %s already banned" % (self.jail.getName(),
logSys.info("[%s] %s already banned" % (self._jail.getName(),
aInfo["ip"]))
return False
##
# Check for IP address to unban.
#
# Unban IP address which are outdated.
def __checkUnBan(self):
"""Check for IP address to unban.
Unban IP addresses which are outdated.
"""
for ticket in self.__banManager.unBanList(MyTime.time()):
self.__unBan(ticket)
##
# Flush the ban list.
#
# Unban all IP address which are still in the banning list.
def __flushBan(self):
"""Flush the ban list.
Unban all IP address which are still in the banning list.
"""
logSys.debug("Flush ban list")
for ticket in self.__banManager.flushBanList():
self.__unBan(ticket)
##
# Unbans host corresponding to the ticket.
#
# Executes the actions in order to unban the host given in the
# ticket.
def __unBan(self, ticket):
"""Unbans host corresponding to the ticket.
Executes the actions in order to unban the host given in the
ticket.
Parameters
----------
ticket : FailTicket
Ticket of failures of which to unban
"""
aInfo = dict()
aInfo["ip"] = ticket.getIP()
aInfo["failures"] = ticket.getAttempt()
aInfo["time"] = ticket.getTime()
aInfo["matches"] = "".join(ticket.getMatches())
logSys.warning("[%s] Unban %s" % (self.jail.getName(), aInfo["ip"]))
for action in self.__actions:
action.execActionUnban(aInfo)
##
# Get the status of the filter.
#
# Get some informations about the filter state such as the total
# number of failures.
# @return a list with tuple
logSys.warning("[%s] Unban %s" % (self._jail.getName(), aInfo["ip"]))
for name, action in self._actions.iteritems():
try:
action.unban(aInfo)
except Exception as e:
logSys.error(
"Failed to execute unban jail '%s' action '%s': %s",
self._jail.getName(), name, e)
def status(self):
"""Get the status of the filter.
Get some informations about the filter state such as the total
number of failures.
Returns
-------
list
List of tuple pairs, each containing a description and value
for general status information.
"""
ret = [("Currently banned", self.__banManager.size()),
("Total banned", self.__banManager.getBanTotal()),
("IP list", self.__banManager.getBanList())]

View File

@ -27,7 +27,7 @@ __license__ = "GPL"
from pickle import dumps, loads, HIGHEST_PROTOCOL
import asyncore, asynchat, socket, os, logging, sys, traceback, fcntl
from fail2ban import helpers
from .. import helpers
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)

View File

@ -24,10 +24,11 @@ __author__ = "Cyril Jaquier"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
from ticket import BanTicket
from threading import Lock
from mytime import MyTime
import logging
from threading import Lock
from .ticket import BanTicket
from .mytime import MyTime
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)

View File

@ -29,8 +29,8 @@ import json
import locale
from functools import wraps
from fail2ban.server.mytime import MyTime
from fail2ban.server.ticket import FailTicket
from .mytime import MyTime
from .ticket import FailTicket
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)

View File

@ -22,10 +22,10 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import sys, time, logging
from datetemplate import DatePatternRegex, DateTai64n, DateEpoch, DateISO8601
from threading import Lock
from .datetemplate import DatePatternRegex, DateTai64n, DateEpoch, DateISO8601
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)

View File

@ -25,14 +25,13 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import re, time, calendar
import logging
from datetime import datetime
from datetime import timedelta
from mytime import MyTime
import iso8601
from .mytime import MyTime
from . import iso8601
import logging
logSys = logging.getLogger(__name__)

View File

@ -24,11 +24,12 @@ __author__ = "Cyril Jaquier"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
from faildata import FailData
from ticket import FailTicket
from threading import Lock
import logging
from .faildata import FailData
from .ticket import FailTicket
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)

View File

@ -21,18 +21,17 @@ __author__ = "Cyril Jaquier and Fail2Ban Contributors"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2013 Yaroslav Halchenko"
__license__ = "GPL"
from failmanager import FailManagerEmpty
from failmanager import FailManager
from ticket import FailTicket
from jailthread import JailThread
from datedetector import DateDetector
from datetemplate import DatePatternRegex, DateISO8601, DateEpoch, DateTai64n
from mytime import MyTime
from failregex import FailRegex, Regex, RegexException
from action import Action
import logging, re, os, fcntl, time, sys, locale, codecs
from .failmanager import FailManagerEmpty, FailManager
from .ticket import FailTicket
from .jailthread import JailThread
from .datedetector import DateDetector
from .datetemplate import DatePatternRegex, DateISO8601, DateEpoch, DateTai64n
from .mytime import MyTime
from .failregex import FailRegex, Regex, RegexException
from .action import CommandAction
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)
@ -378,9 +377,9 @@ class Filter(JailThread):
return True
if self.__ignoreCommand:
command = Action.replaceTag(self.__ignoreCommand, { 'ip': ip } )
command = CommandAction.replaceTag(self.__ignoreCommand, { 'ip': ip } )
logSys.debug('ignore command: ' + command)
return Action.executeCmd(command)
return CommandAction.executeCmd(command)
return False

View File

@ -23,11 +23,13 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2012 Yaroslav Halchenko"
__license__ = "GPL"
from failmanager import FailManagerEmpty
from filter import FileFilter
from mytime import MyTime
import time, logging, fcntl
import time, logging, gamin, fcntl
import gamin
from .failmanager import FailManagerEmpty
from .filter import FileFilter
from .mytime import MyTime
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)

View File

@ -24,12 +24,12 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier; 2012 Yaroslav Halchenko"
__license__ = "GPL"
from failmanager import FailManagerEmpty
from filter import FileFilter
from mytime import MyTime
import time, logging, os
from .failmanager import FailManagerEmpty
from .filter import FileFilter
from .mytime import MyTime
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)

View File

@ -24,13 +24,12 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2012 Lee Clemens, 2012 Y
__license__ = "GPL"
import time, logging, pyinotify
from distutils.version import LooseVersion
from os.path import dirname, sep as pathsep
from failmanager import FailManagerEmpty
from filter import FileFilter
from mytime import MyTime
from .failmanager import FailManagerEmpty
from .filter import FileFilter
from .mytime import MyTime
if not hasattr(pyinotify, '__version__') \

View File

@ -29,9 +29,9 @@ from systemd import journal
if LooseVersion(getattr(journal, '__version__', "0")) < '204':
raise ImportError("Fail2Ban requires systemd >= 204")
from failmanager import FailManagerEmpty
from filter import JournalFilter
from mytime import MyTime
from .failmanager import FailManagerEmpty
from .filter import JournalFilter
from .mytime import MyTime
# Gets the instance of the logger.

View File

@ -25,7 +25,7 @@ __license__ = "GPL"
import Queue, logging
from actions import Actions
from .actions import Actions
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)
@ -71,7 +71,7 @@ class Jail:
"%r was requested" % (b, backend))
else:
logSys.info("Initiated %r backend" % b)
self.__action = Actions(self)
self.__actions = Actions(self)
return # we are done
except ImportError, e:
logSys.debug(
@ -123,11 +123,17 @@ class Jail:
def getDatabase(self):
return self.__db
def getFilter(self):
@property
def filter(self):
"""The filter which the jail is using to monitor log files.
"""
return self.__filter
def getAction(self):
return self.__action
@property
def actions(self):
"""Actions object used to manage actions for jail.
"""
return self.__actions
def putFailTicket(self, ticket):
self.__queue.put(ticket)
@ -142,36 +148,36 @@ class Jail:
def start(self):
self.__filter.start()
self.__action.start()
self.__actions.start()
# Restore any previous valid bans from the database
if self.__db is not None:
for ticket in self.__db.getBans(
jail=self, bantime=self.__action.getBanTime()):
jail=self, bantime=self.__actions.getBanTime()):
self.__queue.put(ticket)
logSys.info("Jail '%s' started" % self.__name)
def stop(self):
self.__filter.stop()
self.__action.stop()
self.__actions.stop()
self.__filter.join()
self.__action.join()
self.__actions.join()
logSys.info("Jail '%s' stopped" % self.__name)
def isAlive(self):
isAlive0 = self.__filter.isAlive()
isAlive1 = self.__action.isAlive()
isAlive1 = self.__actions.isAlive()
return isAlive0 or isAlive1
def setIdle(self, value):
self.__filter.setIdle(value)
self.__action.setIdle(value)
self.__actions.setIdle(value)
def getIdle(self):
return self.__filter.getIdle() or self.__action.getIdle()
return self.__filter.getIdle() or self.__actions.getIdle()
def getStatus(self):
fStatus = self.__filter.status()
aStatus = self.__action.status()
aStatus = self.__actions.status()
ret = [("filter", fStatus),
("action", aStatus)]
return ret

View File

@ -21,137 +21,84 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2013- Yaroslav Halchenko"
__license__ = "GPL"
from fail2ban.exceptions import DuplicateJailException, UnknownJailException
from jail import Jail
from threading import Lock
from collections import Mapping
##
# Handles the jails.
#
# This class handles the jails. Creation, deletion or access to a jail must be
# done through this class. This class is thread-safe which is not the case of
# the jail itself, including filter and actions.
from ..exceptions import DuplicateJailException, UnknownJailException
from .jail import Jail
class Jails(Mapping):
"""Handles the jails.
This class handles the jails. Creation, deletion or access to a jail
must be done through this class. This class is thread-safe which is
not the case of the jail itself, including filter and actions. This
class is based on Mapping type, and the `add` method must be used to
add additional jails.
"""
class Jails:
##
# Constructor.
def __init__(self):
"""Initialise an empty Jails instance.
"""
self.__lock = Lock()
self.__jails = dict()
##
# Adds a jail.
#
# Adds a new jail which should use the given backend. Raises a
# <code>DuplicateJailException</code> if the jail is already defined.
# @param name The name of the jail
# @param backend The backend to use
self._jails = dict()
def add(self, name, backend, db=None):
"""Adds a jail.
Adds a new jail if not already present which should use the
given backend.
Parameters
----------
name : str
The name of the jail.
backend : str
The backend to use.
Raises
------
DuplicateJailException
If jail name is already present.
"""
try:
self.__lock.acquire()
if self.__jails.has_key(name):
if name in self._jails:
raise DuplicateJailException(name)
else:
self.__jails[name] = Jail(name, backend, db)
finally:
self.__lock.release()
##
# Removes a jail.
#
# Removes the jail <code>name</code>. Raise an <code>UnknownJailException</code>
# if the jail does not exist.
# @param name The name of the jail
def remove(self, name):
try:
self.__lock.acquire()
if self.__jails.has_key(name):
del self.__jails[name]
else:
raise UnknownJailException(name)
finally:
self.__lock.release()
##
# Returns a jail.
#
# Returns the jail <code>name</code>. Raise an <code>UnknownJailException</code>
# if the jail does not exist.
# @param name The name of the jail
def get(self, name):
try:
self.__lock.acquire()
if self.__jails.has_key(name):
jail = self.__jails[name]
return jail
else:
raise UnknownJailException(name)
finally:
self.__lock.release()
##
# Returns an action class instance.
#
# Returns the action object of the jail <code>name</code>. Raise an
# <code>UnknownJailException</code> if the jail does not exist.
# @param name The name of the jail
def getAction(self, name):
try:
self.__lock.acquire()
if self.__jails.has_key(name):
action = self.__jails[name].getAction()
return action
else:
raise UnknownJailException(name)
finally:
self.__lock.release()
##
# Returns a filter class instance.
#
# Returns the filter object of the jail <code>name</code>. Raise an
# <code>UnknownJailException</code> if the jail does not exist.
# @param name The name of the jail
def getFilter(self, name):
try:
self.__lock.acquire()
if self.__jails.has_key(name):
action = self.__jails[name].getFilter()
return action
else:
raise UnknownJailException(name)
finally:
self.__lock.release()
##
# Returns the jails.
#
# Returns a copy of the jails list.
def getAll(self):
try:
self.__lock.acquire()
return self.__jails.copy()
finally:
self.__lock.release()
##
# Returns the size of the jails.
#
# Returns the number of jails.
def size(self):
try:
self.__lock.acquire()
return len(self.__jails)
self._jails[name] = Jail(name, backend, db)
finally:
self.__lock.release()
def __getitem__(self, name):
try:
self.__lock.acquire()
return self._jails[name]
except KeyError:
raise UnknownJailException(name)
finally:
self.__lock.release()
def __delitem__(self, name):
try:
self.__lock.acquire()
del self._jails[name]
except KeyError:
raise UnknownJailException(name)
finally:
self.__lock.release()
def __len__(self):
try:
self.__lock.acquire()
return len(self._jails)
finally:
self.__lock.release()
def __iter__(self):
try:
self.__lock.acquire()
return iter(self._jails)
finally:
self.__lock.release()

View File

@ -25,15 +25,16 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
from threading import Lock, RLock
from jails import Jails
from filter import FileFilter, JournalFilter
from transmitter import Transmitter
from asyncserver import AsyncServer
from asyncserver import AsyncServerException
from database import Fail2BanDb
from fail2ban import version
import logging, logging.handlers, sys, os, signal
from .jails import Jails
from .filter import FileFilter, JournalFilter
from .transmitter import Transmitter
from .asyncserver import AsyncServer, AsyncServerException
from .database import Fail2BanDb
from .action import CommandAction
from .. import version
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)
@ -121,10 +122,10 @@ class Server:
def addJail(self, name, backend):
self.__jails.add(name, backend, self.__db)
if self.__db is not None:
self.__db.addJail(self.__jails.get(name))
self.__db.addJail(self.__jails[name])
def delJail(self, name):
self.__jails.remove(name)
del self.__jails[name]
if self.__db is not None:
self.__db.delJailName(name)
@ -132,7 +133,7 @@ class Server:
try:
self.__lock.acquire()
if not self.isAlive(name):
self.__jails.get(name).start()
self.__jails[name].start()
finally:
self.__lock.release()
@ -141,7 +142,7 @@ class Server:
try:
self.__lock.acquire()
if self.isAlive(name):
self.__jails.get(name).stop()
self.__jails[name].stop()
self.delJail(name)
finally:
self.__lock.release()
@ -150,43 +151,43 @@ class Server:
logSys.info("Stopping all jails")
try:
self.__lock.acquire()
for jail in self.__jails.getAll():
for jail in self.__jails.keys():
self.stopJail(jail)
finally:
self.__lock.release()
def isAlive(self, name):
return self.__jails.get(name).isAlive()
return self.__jails[name].isAlive()
def setIdleJail(self, name, value):
self.__jails.get(name).setIdle(value)
self.__jails[name].setIdle(value)
return True
def getIdleJail(self, name):
return self.__jails.get(name).getIdle()
return self.__jails[name].getIdle()
# Filter
def addIgnoreIP(self, name, ip):
self.__jails.getFilter(name).addIgnoreIP(ip)
self.__jails[name].filter.addIgnoreIP(ip)
def delIgnoreIP(self, name, ip):
self.__jails.getFilter(name).delIgnoreIP(ip)
self.__jails[name].filter.delIgnoreIP(ip)
def getIgnoreIP(self, name):
return self.__jails.getFilter(name).getIgnoreIP()
return self.__jails[name].filter.getIgnoreIP()
def addLogPath(self, name, fileName, tail=False):
filter_ = self.__jails.getFilter(name)
filter_ = self.__jails[name].filter
if isinstance(filter_, FileFilter):
filter_.addLogPath(fileName, tail)
def delLogPath(self, name, fileName):
filter_ = self.__jails.getFilter(name)
filter_ = self.__jails[name].filter
if isinstance(filter_, FileFilter):
filter_.delLogPath(fileName)
def getLogPath(self, name):
filter_ = self.__jails.getFilter(name)
filter_ = self.__jails[name].filter
if isinstance(filter_, FileFilter):
return [m.getFileName()
for m in filter_.getLogPath()]
@ -195,17 +196,17 @@ class Server:
return []
def addJournalMatch(self, name, match): # pragma: systemd no cover
filter_ = self.__jails.getFilter(name)
filter_ = self.__jails[name].filter
if isinstance(filter_, JournalFilter):
filter_.addJournalMatch(match)
def delJournalMatch(self, name, match): # pragma: systemd no cover
filter_ = self.__jails.getFilter(name)
filter_ = self.__jails[name].filter
if isinstance(filter_, JournalFilter):
filter_.delJournalMatch(match)
def getJournalMatch(self, name): # pragma: systemd no cover
filter_ = self.__jails.getFilter(name)
filter_ = self.__jails[name].filter
if isinstance(filter_, JournalFilter):
return filter_.getJournalMatch()
else:
@ -213,154 +214,109 @@ class Server:
return []
def setLogEncoding(self, name, encoding):
filter_ = self.__jails.getFilter(name)
filter_ = self.__jails[name].filter
if isinstance(filter_, FileFilter):
filter_.setLogEncoding(encoding)
def getLogEncoding(self, name):
filter_ = self.__jails.getFilter(name)
filter_ = self.__jails[name].filter
if isinstance(filter_, FileFilter):
return filter_.getLogEncoding()
def setFindTime(self, name, value):
self.__jails.getFilter(name).setFindTime(value)
self.__jails[name].filter.setFindTime(value)
def getFindTime(self, name):
return self.__jails.getFilter(name).getFindTime()
return self.__jails[name].filter.getFindTime()
def setDatePattern(self, name, pattern):
self.__jails.getFilter(name).setDatePattern(pattern)
self.__jails[name].filter.setDatePattern(pattern)
def getDatePattern(self, name):
return self.__jails.getFilter(name).getDatePattern()
return self.__jails[name].filter.getDatePattern()
def setIgnoreCommand(self, name, value):
self.__jails.getFilter(name).setIgnoreCommand(value)
self.__jails[name].filter.setIgnoreCommand(value)
def getIgnoreCommand(self, name):
return self.__jails.getFilter(name).getIgnoreCommand()
return self.__jails[name].filter.getIgnoreCommand()
def addFailRegex(self, name, value):
self.__jails.getFilter(name).addFailRegex(value)
self.__jails[name].filter.addFailRegex(value)
def delFailRegex(self, name, index):
self.__jails.getFilter(name).delFailRegex(index)
self.__jails[name].filter.delFailRegex(index)
def getFailRegex(self, name):
return self.__jails.getFilter(name).getFailRegex()
return self.__jails[name].filter.getFailRegex()
def addIgnoreRegex(self, name, value):
self.__jails.getFilter(name).addIgnoreRegex(value)
self.__jails[name].filter.addIgnoreRegex(value)
def delIgnoreRegex(self, name, index):
self.__jails.getFilter(name).delIgnoreRegex(index)
self.__jails[name].filter.delIgnoreRegex(index)
def getIgnoreRegex(self, name):
return self.__jails.getFilter(name).getIgnoreRegex()
return self.__jails[name].filter.getIgnoreRegex()
def setUseDns(self, name, value):
self.__jails.getFilter(name).setUseDns(value)
self.__jails[name].filter.setUseDns(value)
def getUseDns(self, name):
return self.__jails.getFilter(name).getUseDns()
return self.__jails[name].filter.getUseDns()
def setMaxRetry(self, name, value):
self.__jails.getFilter(name).setMaxRetry(value)
self.__jails[name].filter.setMaxRetry(value)
def getMaxRetry(self, name):
return self.__jails.getFilter(name).getMaxRetry()
return self.__jails[name].filter.getMaxRetry()
def setMaxLines(self, name, value):
self.__jails.getFilter(name).setMaxLines(value)
self.__jails[name].filter.setMaxLines(value)
def getMaxLines(self, name):
return self.__jails.getFilter(name).getMaxLines()
return self.__jails[name].filter.getMaxLines()
# Action
def addAction(self, name, value):
self.__jails.getAction(name).addAction(value)
def getLastAction(self, name):
return self.__jails.getAction(name).getLastAction()
def addAction(self, name, value, *args):
self.__jails[name].actions.add(value, *args)
def getActions(self, name):
return self.__jails.getAction(name).getActions()
return self.__jails[name].actions
def delAction(self, name, value):
self.__jails.getAction(name).delAction(value)
del self.__jails[name].actions[value]
def setCInfo(self, name, action, key, value):
self.__jails.getAction(name).getAction(action).setCInfo(key, value)
def getCInfo(self, name, action, key):
return self.__jails.getAction(name).getAction(action).getCInfo(key)
def delCInfo(self, name, action, key):
self.__jails.getAction(name).getAction(action).delCInfo(key)
def getAction(self, name, value):
return self.__jails[name].actions[value]
def setBanTime(self, name, value):
self.__jails.getAction(name).setBanTime(value)
self.__jails[name].actions.setBanTime(value)
def setBanIP(self, name, value):
return self.__jails.getFilter(name).addBannedIP(value)
return self.__jails[name].filter.addBannedIP(value)
def setUnbanIP(self, name, value):
return self.__jails.getAction(name).removeBannedIP(value)
self.__jails[name].actions.removeBannedIP(value)
def getBanTime(self, name):
return self.__jails.getAction(name).getBanTime()
return self.__jails[name].actions.getBanTime()
def setActionStart(self, name, action, value):
self.__jails.getAction(name).getAction(action).setActionStart(value)
def getActionStart(self, name, action):
return self.__jails.getAction(name).getAction(action).getActionStart()
def setActionStop(self, name, action, value):
self.__jails.getAction(name).getAction(action).setActionStop(value)
def getActionStop(self, name, action):
return self.__jails.getAction(name).getAction(action).getActionStop()
def setActionCheck(self, name, action, value):
self.__jails.getAction(name).getAction(action).setActionCheck(value)
def getActionCheck(self, name, action):
return self.__jails.getAction(name).getAction(action).getActionCheck()
def setActionBan(self, name, action, value):
self.__jails.getAction(name).getAction(action).setActionBan(value)
def getActionBan(self, name, action):
return self.__jails.getAction(name).getAction(action).getActionBan()
def setActionUnban(self, name, action, value):
self.__jails.getAction(name).getAction(action).setActionUnban(value)
def getActionUnban(self, name, action):
return self.__jails.getAction(name).getAction(action).getActionUnban()
def setActionTimeout(self, name, action, value):
self.__jails.getAction(name).getAction(action).setTimeout(value)
def getActionTimeout(self, name, action):
return self.__jails.getAction(name).getAction(action).getTimeout()
# Status
def status(self):
try:
self.__lock.acquire()
jails = list(self.__jails.getAll())
jails = list(self.__jails)
jails.sort()
jailList = ", ".join(jails)
ret = [("Number of jail", self.__jails.size()),
ret = [("Number of jail", len(self.__jails)),
("Jail list", jailList)]
return ret
finally:
self.__lock.release()
def statusJail(self, name):
return self.__jails.get(name).getStatus()
return self.__jails[name].getStatus()
# Logging
@ -488,7 +444,7 @@ class Server:
return "flushed"
def setDatabase(self, filename):
if self.__jails.size() == 0:
if len(self.__jails) == 0:
if filename.lower() == "none":
self.__db = None
else:

View File

@ -25,6 +25,7 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import logging, time
import json
# Gets the instance of the logger.
logSys = logging.getLogger(__name__)
@ -226,56 +227,29 @@ class Transmitter:
return self.__server.setBanIP(name,value)
elif command[1] == "unbanip":
value = command[2]
return self.__server.setUnbanIP(name,value)
self.__server.setUnbanIP(name, value)
return value
elif command[1] == "addaction":
value = command[2]
self.__server.addAction(name, value)
return self.__server.getLastAction(name).getName()
args = [command[2]]
if len(command) > 3:
args.extend([command[3], json.loads(command[4])])
self.__server.addAction(name, *args)
return args[0]
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):
@ -327,31 +301,25 @@ class Transmitter:
elif command[1] == "bantime":
return self.__server.getBanTime(name)
elif command[1] == "actions":
return self.__server.getActions(name)
elif command[1] == "addaction":
return self.__server.getLastAction(name).getName()
elif command[1] == "actionstart":
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.getActions(name).keys()
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

@ -26,39 +26,49 @@ __license__ = "GPL"
import unittest, time
import sys, os, tempfile
from fail2ban.server.actions import Actions
from dummyjail import DummyJail
class ExecuteActions(unittest.TestCase):
from ..server.actions import Actions
from .dummyjail import DummyJail
from .utils import LogCaptureTestCase
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
class ExecuteActions(LogCaptureTestCase):
def setUp(self):
"""Call before every test case."""
super(ExecuteActions, self).setUp()
self.__jail = DummyJail()
self.__actions = Actions(self.__jail)
self.__tmpfile, self.__tmpfilename = tempfile.mkstemp()
def tearDown(self):
super(ExecuteActions, self).tearDown()
os.remove(self.__tmpfilename)
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.__actions.add('ip')
self.__ip = self.__actions['ip']
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 testActionsAddDuplicateName(self):
self.__actions.add('test')
self.assertRaises(ValueError, self.__actions.add, 'test')
def testActionsManipulation(self):
self.__actions.addAction('test')
self.assertTrue(self.__actions.getAction('test'))
self.assertTrue(self.__actions.getLastAction())
self.assertRaises(KeyError,self.__actions.getAction,*['nonexistant action'])
self.__actions.addAction('test1')
self.__actions.delAction('test')
self.__actions.delAction('test1')
self.assertRaises(KeyError, self.__actions.getAction, *['test'])
self.assertRaises(IndexError,self.__actions.getLastAction)
self.__actions.add('test')
self.assertTrue(self.__actions['test'])
self.assertTrue('test' in self.__actions)
self.assertFalse('nonexistant action' in self.__actions)
self.__actions.add('test1')
del self.__actions['test']
del self.__actions['test1']
self.assertFalse('test' in self.__actions)
self.assertEqual(len(self.__actions), 0)
self.__actions.setBanTime(127)
self.assertEqual(self.__actions.getBanTime(),127)
@ -77,3 +87,55 @@ class ExecuteActions(unittest.TestCase):
self.assertEqual(self.__actions.status(),[("Currently banned", 0 ),
("Total banned", 0 ), ("IP list", [] )])
def testAddActionPython(self):
self.__actions.add(
"Action", os.path.join(TEST_FILES_DIR, "action.d/action.py"),
{'opt1': 'value'})
self.assertTrue(self._is_logged("TestAction initialised"))
self.__actions.start()
time.sleep(3)
self.assertTrue(self._is_logged("TestAction action start"))
self.__actions.stop()
self.__actions.join()
self.assertTrue(self._is_logged("TestAction action stop"))
self.assertRaises(IOError,
self.__actions.add, "Action3", "/does/not/exist.py", {})
# With optional argument
self.__actions.add(
"Action4", os.path.join(TEST_FILES_DIR, "action.d/action.py"),
{'opt1': 'value', 'opt2': 'value2'})
# With too many arguments
self.assertRaises(
TypeError, self.__actions.add, "Action5",
os.path.join(TEST_FILES_DIR, "action.d/action.py"),
{'opt1': 'value', 'opt2': 'value2', 'opt3': 'value3'})
# Missing required argument
self.assertRaises(
TypeError, self.__actions.add, "Action5",
os.path.join(TEST_FILES_DIR, "action.d/action.py"), {})
def testAddPythonActionNOK(self):
self.assertRaises(RuntimeError, self.__actions.add,
"Action", os.path.join(TEST_FILES_DIR,
"action.d/action_noAction.py"),
{})
self.assertRaises(RuntimeError, self.__actions.add,
"Action", os.path.join(TEST_FILES_DIR,
"action.d/action_nomethod.py"),
{})
self.__actions.add(
"Action", os.path.join(TEST_FILES_DIR,
"action.d/action_errors.py"),
{})
self.__actions.start()
time.sleep(3)
self.assertTrue(self._is_logged("Failed to start"))
self.__actions.stop()
self.__actions.join()
self.assertTrue(self._is_logged("Failed to stop"))

View File

@ -27,27 +27,22 @@ __license__ = "GPL"
import time
import logging, sys
from fail2ban.server.action import Action
from ..server.action import CommandAction, CallingMap
from fail2ban.tests.utils import LogCaptureTestCase
from .utils import LogCaptureTestCase
class ExecuteAction(LogCaptureTestCase):
class CommandActionTest(LogCaptureTestCase):
def setUp(self):
"""Call before every test case."""
self.__action = Action("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 testNameChange(self):
self.assertEqual(self.__action.getName(), "Test")
self.__action.setName("Tricky Test")
self.assertEqual(self.__action.getName(), "Tricky Test")
def testSubstituteRecursiveTags(self):
aInfo = {
'HOST': "192.0.2.0",
@ -55,15 +50,15 @@ class ExecuteAction(LogCaptureTestCase):
'xyz': "890 <ABC>",
}
# Recursion is bad
self.assertFalse(Action.substituteRecursiveTags({'A': '<A>'}))
self.assertFalse(Action.substituteRecursiveTags({'A': '<B>', 'B': '<A>'}))
self.assertFalse(Action.substituteRecursiveTags({'A': '<B>', 'B': '<C>', 'C': '<A>'}))
self.assertFalse(CommandAction.substituteRecursiveTags({'A': '<A>'}))
self.assertFalse(CommandAction.substituteRecursiveTags({'A': '<B>', 'B': '<A>'}))
self.assertFalse(CommandAction.substituteRecursiveTags({'A': '<B>', 'B': '<C>', 'C': '<A>'}))
# missing tags are ok
self.assertEqual(Action.substituteRecursiveTags({'A': '<C>'}), {'A': '<C>'})
self.assertEqual(Action.substituteRecursiveTags({'A': '<C> <D> <X>','X':'fun'}), {'A': '<C> <D> fun', 'X':'fun'})
self.assertEqual(Action.substituteRecursiveTags({'A': '<C> <B>', 'B': 'cool'}), {'A': '<C> cool', 'B': 'cool'})
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C>'}), {'A': '<C>'})
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C> <D> <X>','X':'fun'}), {'A': '<C> <D> fun', 'X':'fun'})
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C> <B>', 'B': 'cool'}), {'A': '<C> cool', 'B': 'cool'})
# rest is just cool
self.assertEqual(Action.substituteRecursiveTags(aInfo),
self.assertEqual(CommandAction.substituteRecursiveTags(aInfo),
{ 'HOST': "192.0.2.0",
'ABC': '123 192.0.2.0',
'xyz': '890 123 192.0.2.0',
@ -99,90 +94,102 @@ class ExecuteAction(LogCaptureTestCase):
# Callable
self.assertEqual(
self.__action.replaceTag("09 <callable> 11",
{'callable': lambda: str(10)}),
self.__action.replaceTag("09 <callme> 11",
CallingMap(callme=lambda: str(10))),
"09 10 11")
# As tag not present, therefore callable should not be called
# Will raise ValueError if it is
self.assertEqual(
self.__action.replaceTag("abc",
{'callable': lambda: int("a")}), "abc")
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):
Action.executeCmd('/bin/ls >/dev/null\nbogusXXX now 2>/dev/null')
CommandAction.executeCmd('/bin/ls >/dev/null\nbogusXXX now 2>/dev/null')
self.assertTrue(self._is_logged('HINT on 127: "Command not found"'))
def testExecuteTimeout(self):
stime = time.time()
Action.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'))
def testCaptureStdOutErr(self):
Action.executeCmd('echo "How now brown cow"')
CommandAction.executeCmd('echo "How now brown cow"')
self.assertTrue(self._is_logged("'How now brown cow\\n'"))
Action.executeCmd(
CommandAction.executeCmd(
'echo "The rain in Spain stays mainly in the plain" 1>&2')
self.assertTrue(self._is_logged(
"'The rain in Spain stays mainly in the plain\\n'"))
def testCallingMap(self):
mymap = CallingMap(callme=lambda: str(10), error=lambda: int('a'),
dontcallme= "string", number=17)
# Should work fine
self.assertEqual(
"%(callme)s okay %(dontcallme)s %(number)i" % mymap,
"10 okay string 17")
# Error will now trip, demonstrating delayed call
self.assertRaises(ValueError, lambda x: "%(error)i" % x, mymap)

View File

@ -26,8 +26,8 @@ __license__ = "GPL"
import unittest
from fail2ban.server.banmanager import BanManager
from fail2ban.server.ticket import BanTicket
from ..server.banmanager import BanManager
from ..server.ticket import BanTicket
class AddFailure(unittest.TestCase):

View File

@ -23,13 +23,13 @@ __license__ = "GPL"
import os, shutil, sys, tempfile, unittest
from fail2ban.client.configreader import ConfigReader
from fail2ban.client.jailreader import JailReader
from fail2ban.client.filterreader import FilterReader
from fail2ban.client.jailsreader import JailsReader
from fail2ban.client.actionreader import ActionReader
from fail2ban.client.configurator import Configurator
from fail2ban.tests.utils import LogCaptureTestCase
from ..client.configreader import ConfigReader
from ..client.jailreader import JailReader
from ..client.filterreader import FilterReader
from ..client.jailsreader import JailsReader
from ..client.actionreader import ActionReader
from ..client.configurator import Configurator
from .utils import LogCaptureTestCase
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
if os.path.exists(os.path.join('config','fail2ban.conf')):
@ -356,13 +356,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],
@ -489,7 +494,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):
@ -542,6 +547,8 @@ class JailsReaderTest(LogCaptureTestCase):
[testjail1]
action = testaction1[actname=test1]
testaction1[actname=test2]
testaction.py
testaction.py[actname=test3]
filter = testfilter1
""")
jailfd.close()
@ -550,8 +557,12 @@ filter = testfilter1
self.assertTrue(jails.getOptions())
comm_commands = jails.convert(allow_no_files=True)
action_names = [comm[-1] for comm in comm_commands if comm[:3] == ['set', 'testjail1', 'addaction']]
add_actions = [comm[3:] for comm in comm_commands
if comm[:3] == ['set', 'testjail1', 'addaction']]
self.assertNotEqual(len(set(action_names)), 1)
self.assertEqual(len(set(action[0] for action in add_actions)), 4)
# Python actions should not be passed `actname`
self.assertEqual(add_actions[-1][-1], "{}")
shutil.rmtree(basedir)

View File

@ -28,11 +28,11 @@ import tempfile
import sqlite3
import shutil
from fail2ban.server.database import Fail2BanDb
from fail2ban.server.filter import FileContainer
from fail2ban.server.mytime import MyTime
from fail2ban.server.ticket import FailTicket
from fail2ban.tests.dummyjail import DummyJail
from ..server.database import Fail2BanDb
from ..server.filter import FileContainer
from ..server.mytime import MyTime
from ..server.ticket import FailTicket
from .dummyjail import DummyJail
class DatabaseTest(unittest.TestCase):

View File

@ -25,10 +25,10 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import unittest, calendar, time, datetime, re, pprint
from fail2ban.server.datedetector import DateDetector
from fail2ban.server.datetemplate import DateTemplate
from fail2ban.server.iso8601 import Utc
from fail2ban.tests.utils import setUpMyTime, tearDownMyTime
from ..server.datedetector import DateDetector
from ..server.datetemplate import DateTemplate
from ..server.iso8601 import Utc
from .utils import setUpMyTime, tearDownMyTime
class DateDetectorTest(unittest.TestCase):

View File

@ -26,8 +26,8 @@ __license__ = "GPL"
import unittest, socket, time, pickle
from fail2ban.server.failmanager import FailManager, FailManagerEmpty
from fail2ban.server.ticket import FailTicket
from ..server.failmanager import FailManager, FailManagerEmpty
from ..server.ticket import FailTicket
class AddFailure(unittest.TestCase):

View File

@ -0,0 +1,28 @@
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__)
self.opt1 = opt1
self.opt2 = opt2
self._opt3 = "Hello"
def start(self):
self._logSys.debug("%s action start" % self.__class__.__name__)
def stop(self):
self._logSys.debug("%s action stop" % self.__class__.__name__)
def ban(self, aInfo):
self._logSys.debug("%s action ban" % 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

@ -0,0 +1,21 @@
from fail2ban.server.action import ActionBase
class TestAction(ActionBase):
def __init__(self, jail, name):
super(TestAction, self).__init__(jail, name)
def start(self):
raise Exception()
def stop(self):
raise Exception()
def ban(self):
raise Exception()
def unban(self):
raise Exception()
Action = TestAction

View File

@ -0,0 +1,5 @@
from fail2ban.server.action import ActionBase
class TestAction(ActionBase):
pass

View File

@ -0,0 +1,12 @@
from fail2ban.server.action import ActionBase
class TestAction():
def __init__(self, jail, name):
pass
def start(self):
pass
Action = TestAction

View File

@ -34,19 +34,16 @@ try:
except ImportError:
journal = None
from fail2ban.server.jail import Jail
from fail2ban.server.filterpoll import FilterPoll
from fail2ban.server.filter import Filter, FileFilter, DNSUtils
from fail2ban.server.failmanager import FailManager
from fail2ban.server.failmanager import FailManagerEmpty
from fail2ban.server.mytime import MyTime
from fail2ban.tests.utils import setUpMyTime, tearDownMyTime
from fail2ban.tests.utils import mtimesleep, LogCaptureTestCase
from ..server.jail import Jail
from ..server.filterpoll import FilterPoll
from ..server.filter import Filter, FileFilter, DNSUtils
from ..server.failmanager import FailManager, FailManagerEmpty
from ..server.mytime import MyTime
from .utils import setUpMyTime, tearDownMyTime, mtimesleep, LogCaptureTestCase
from .dummyjail import DummyJail
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
from fail2ban.tests.dummyjail import DummyJail
# yoh: per Steven Hiscocks's insight while troubleshooting
# https://github.com/fail2ban/fail2ban/issues/103#issuecomment-15542836
# adding a sufficiently large buffer might help to guarantee that

View File

@ -28,8 +28,8 @@ import shutil
from glob import glob
from utils import mbasename, TraceBack, FormatterWithTraceBack
from fail2ban.helpers import formatExceptionInfo
from .utils import mbasename, TraceBack, FormatterWithTraceBack
from ..helpers import formatExceptionInfo
class HelpersTest(unittest.TestCase):

View File

@ -30,9 +30,9 @@ else:
import simplejson as json
next = lambda x: x.next()
from fail2ban.server.filter import Filter
from fail2ban.client.filterreader import FilterReader
from fail2ban.tests.utils import setUpMyTime, tearDownMyTime
from ..server.filter import Filter
from ..client.filterreader import FilterReader
from .utils import setUpMyTime, tearDownMyTime
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
if os.path.exists('config/fail2ban.conf'):

View File

@ -26,11 +26,11 @@ __license__ = "GPL"
import unittest, socket, time, tempfile, os, locale, sys, logging
from fail2ban.server.failregex import Regex, FailRegex, RegexException
from fail2ban.server.server import Server, logSys
from fail2ban.server.jail import Jail
from fail2ban.exceptions import UnknownJailException
from fail2ban.tests.utils import LogCaptureTestCase
from ..server.failregex import Regex, FailRegex, RegexException
from ..server.server import Server, logSys
from ..server.jail import Jail
from ..exceptions import UnknownJailException
from .utils import LogCaptureTestCase
#from bin.fail2ban-client import Fail2banClient
try:
from fail2ban.server import filtersystemd
@ -518,45 +518,38 @@ class Transmitter(TransmitterBase):
self.assertEqual(
self.transm.proceed(["set", self.jailName, "addaction", action]),
(0, action))
self.assertEqual(
self.transm.proceed(["get", self.jailName, "addaction"]),
(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))
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]),
@ -565,6 +558,42 @@ class Transmitter(TransmitterBase):
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))
self.assertEqual(
sorted(self.transm.proceed(["get", self.jailName,
"actionproperties", action])[1]),
['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])[1]),
['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

@ -26,8 +26,8 @@ __license__ = "GPL"
import unittest, time, tempfile, os, threading
from fail2ban.server.asyncserver import AsyncServer, AsyncServerException
from fail2ban.client.csocket import CSocket
from ..server.asyncserver import AsyncServer, AsyncServerException
from ..client.csocket import CSocket
class Socket(unittest.TestCase):

View File

@ -25,16 +25,9 @@ __license__ = "GPL"
import logging, os, re, traceback, time, unittest, sys
from os.path import basename, dirname
from StringIO import StringIO
import json
if sys.version_info >= (2, 6):
import json
else:
try:
import simplejson as json
except ImportError:
json = None
from fail2ban.server.mytime import MyTime
from ..server.mytime import MyTime
logSys = logging.getLogger(__name__)
@ -138,19 +131,18 @@ def tearDownMyTime():
def gatherTests(regexps=None, no_network=False):
# Import all the test cases here instead of a module level to
# avoid circular imports
from fail2ban.tests import banmanagertestcase
from fail2ban.tests import clientreadertestcase
from fail2ban.tests import failmanagertestcase
from fail2ban.tests import filtertestcase
from fail2ban.tests import servertestcase
from fail2ban.tests import datedetectortestcase
from fail2ban.tests import actiontestcase
from fail2ban.tests import actionstestcase
from fail2ban.tests import sockettestcase
from fail2ban.tests import misctestcase
from fail2ban.tests import databasetestcase
if json:
from fail2ban.tests import samplestestcase
from . import banmanagertestcase
from . import clientreadertestcase
from . import failmanagertestcase
from . import filtertestcase
from . import servertestcase
from . import datedetectortestcase
from . import actiontestcase
from . import actionstestcase
from . import sockettestcase
from . import misctestcase
from . import databasetestcase
from . import samplestestcase
if not regexps: # pragma: no cover
tests = unittest.TestSuite()
@ -172,7 +164,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))
@ -207,30 +199,27 @@ def gatherTests(regexps=None, no_network=False):
# DateDetector
tests.addTest(unittest.makeSuite(datedetectortestcase.DateDetectorTest))
if json:
# Filter Regex tests with sample logs
tests.addTest(unittest.makeSuite(samplestestcase.FilterSamplesRegex))
else:
logSys.warning("I: Skipping filter samples testing. No simplejson/json module")
# Filter Regex tests with sample logs
tests.addTest(unittest.makeSuite(samplestestcase.FilterSamplesRegex))
#
# Extensive use-tests of different available filters backends
#
from fail2ban.server.filterpoll import FilterPoll
from ..server.filterpoll import FilterPoll
filters = [FilterPoll] # always available
# Additional filters available only if external modules are available
# yoh: Since I do not know better way for parametric tests
# with good old unittest
try:
from fail2ban.server.filtergamin import FilterGamin
from ..server.filtergamin import FilterGamin
filters.append(FilterGamin)
except Exception, e: # pragma: no cover
logSys.warning("Skipping gamin backend testing. Got exception '%s'" % e)
try:
from fail2ban.server.filterpyinotify import FilterPyinotify
from ..server.filterpyinotify import FilterPyinotify
filters.append(FilterPyinotify)
except Exception, e: # pragma: no cover
logSys.warning("I: Skipping pyinotify backend testing. Got exception '%s'" % e)
@ -239,7 +228,7 @@ def gatherTests(regexps=None, no_network=False):
tests.addTest(unittest.makeSuite(
filtertestcase.get_monitor_failures_testcase(Filter_)))
try: # pragma: systemd no cover
from fail2ban.server.filtersystemd import FilterSystemd
from ..server.filtersystemd import FilterSystemd
tests.addTest(unittest.makeSuite(filtertestcase.get_monitor_failures_journal_testcase(FilterSystemd)))
except Exception, e: # pragma: no cover
logSys.warning("I: Skipping systemd backend testing. Got exception '%s'" % e)

View File

@ -7,7 +7,7 @@ jail.conf \- configuration for the fail2ban server
.I jail.conf / jail.local
.I action.d/*.conf action.d/*.local
.I action.d/*.conf action.d/*.local action.d/*.py
.I filter.d/*.conf filter.d/*.local
.SH DESCRIPTION
@ -113,13 +113,15 @@ 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. In the case where multiple of the same action are to be used, the \fBactname\fR option can be assigned to the action to avoid duplicatione.g.:
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]
enabled = true
action = sendmail[name=ssh, dest=john@example.com, actname=mail-john]
sendmail[name=ssh, dest=paul@example.com, actname=mail-paul]
smtp.py[dest=chris@example.com, actname=smtp-chris]
smtp.py[dest=sally@example.com, actname=smtp-sally]
.fi
.SH "ACTION FILES"
@ -160,6 +162,8 @@ 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.
.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/*.conf")
glob("config/action.d/*.conf") +
glob("config/action.d/*.py")
),
('/etc/fail2ban/fail2ban.d',
''