mirror of https://github.com/fail2ban/fail2ban
MRG: from python-actions
commit
d61734b9ac
|
@ -0,0 +1,144 @@
|
|||
|
||||
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):
|
||||
|
||||
def __init__(
|
||||
self, jail, name, host="localhost", user=None, password=None,
|
||||
sendername="Fail2Ban", sender="fail2ban", dest="root", matches=None):
|
||||
|
||||
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.getAction().getBanTime,
|
||||
)
|
||||
|
||||
def _sendMessage(self, subject, text):
|
||||
msg = MIMEText(text)
|
||||
msg['Subject'] = subject
|
||||
msg['From'] = formataddr((self.fromname, self.fromaddr))
|
||||
msg['To'] = self.toaddr
|
||||
msg['Date'] = formatdate()
|
||||
|
||||
smtp = smtplib.SMTP()
|
||||
try:
|
||||
self.logSys.debug("Connected to SMTP '%s', response: %i: %s",
|
||||
*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:
|
||||
smtp.quit()
|
||||
except smtplib.SMTPServerDisconnected:
|
||||
pass # Not connected
|
||||
|
||||
def execActionStart(self):
|
||||
self._sendMessage(
|
||||
"[Fail2Ban] %(jailname)s: started on %(hostname)s" %
|
||||
self.message_values,
|
||||
messages['start'] % self.message_values)
|
||||
|
||||
def execActionStop(self):
|
||||
self._sendMessage(
|
||||
"[Fail2Ban] %(jailname)s: stopped on %(hostname)s" %
|
||||
self.message_values,
|
||||
messages['stop'] % self.message_values)
|
||||
|
||||
def execActionBan(self, aInfo):
|
||||
aInfo.update(self.message_values)
|
||||
message = "".join([
|
||||
messages['ban']['head'],
|
||||
messages['ban'].get(self.matches, ""),
|
||||
messages['ban']['tail']
|
||||
])
|
||||
self._sendMessage(
|
||||
"[Fail2Ban] %(jailname)s: banned %(ip)s from %(hostname)s" %
|
||||
aInfo,
|
||||
message % aInfo)
|
||||
|
||||
Action = SMTPAction
|
|
@ -25,7 +25,8 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
|||
__license__ = "GPL"
|
||||
|
||||
import logging, os
|
||||
from configreader import ConfigReader, DefinitionInitConfigReader
|
||||
|
||||
from fail2ban.client.configreader import ConfigReader, DefinitionInitConfigReader
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
|
|
@ -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 fail2ban.client.configparserinc import SafeConfigParserWithIncludes
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -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 fail2ban.client.configreader import ConfigReader
|
||||
from fail2ban.client.fail2banreader import Fail2banReader
|
||||
from fail2ban.client.jailsreader import JailsReader
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
|
|
@ -25,7 +25,8 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
|||
__license__ = "GPL"
|
||||
|
||||
import logging
|
||||
from configreader import ConfigReader
|
||||
|
||||
from fail2ban.client.configreader import ConfigReader
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
|
|
@ -24,9 +24,8 @@ __author__ = "Cyril Jaquier"
|
|||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||
__license__ = "GPL"
|
||||
|
||||
import logging, os, shlex
|
||||
from configreader import ConfigReader, DefinitionInitConfigReader
|
||||
from fail2ban.server.action import Action
|
||||
from fail2ban.client.configreader import ConfigReader, DefinitionInitConfigReader
|
||||
from fail2ban.server.action import CommandAction
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
@ -44,7 +43,7 @@ class FilterReader(DefinitionInitConfigReader):
|
|||
def convert(self):
|
||||
stream = list()
|
||||
combinedopts = dict(list(self._opts.items()) + list(self._initOpts.items()))
|
||||
opts = Action.substituteRecursiveTags(combinedopts)
|
||||
opts = CommandAction.substituteRecursiveTags(combinedopts)
|
||||
if not opts:
|
||||
raise ValueError('recursive tag definitions unable to be resolved')
|
||||
for opt, value in opts.iteritems():
|
||||
|
|
|
@ -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 fail2ban.client.configreader import ConfigReader
|
||||
from fail2ban.client.filterreader import FilterReader
|
||||
from fail2ban.client.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.get("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
|
||||
|
||||
|
|
|
@ -25,8 +25,9 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
|||
__license__ = "GPL"
|
||||
|
||||
import logging
|
||||
from configreader import ConfigReader
|
||||
from jailreader import JailReader
|
||||
|
||||
from fail2ban.client.configreader import ConfigReader
|
||||
from fail2ban.client.jailreader import JailReader
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
|
|
@ -76,7 +76,7 @@ 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> addaction <ACT> [<PYTHONFILE> <JSONOPTS>]", "adds a new action named <NAME> for <JAIL>. Optionally for a python based action, a <PYTHONFILE> and <JSONOPTS> can be specified"],
|
||||
["set <JAIL> delaction <ACT>", "removes the action <NAME> from <JAIL>"],
|
||||
["set <JAIL> setcinfo <ACT> <KEY> <VALUE>", "sets <VALUE> for <KEY> of the action <NAME> for <JAIL>"],
|
||||
["set <JAIL> delcinfo <ACT> <KEY>", "removes <KEY> for the action <NAME> for <JAIL>"],
|
||||
|
@ -125,13 +125,15 @@ def printFormatted():
|
|||
print
|
||||
firstHeading = True
|
||||
first = True
|
||||
for n in textwrap.wrap(m[1], WIDTH):
|
||||
if len(m[0]) > MARGIN+INDENT:
|
||||
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
|
||||
first = False
|
||||
else:
|
||||
line = ' ' * (INDENT + MARGIN) + n
|
||||
print line
|
||||
print line.rstrip()
|
||||
|
||||
##
|
||||
# Prints the protocol in a "mediawiki" format.
|
||||
|
|
|
@ -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.
|
||||
|
@ -46,6 +48,24 @@ _RETCODE_HINTS = {
|
|||
signame = dict((num, name)
|
||||
for name, num in signal.__dict__.iteritems() if name.startswith("SIG"))
|
||||
|
||||
class CallingMap(MutableMapping):
|
||||
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)
|
||||
|
||||
##
|
||||
# Execute commands.
|
||||
#
|
||||
|
@ -53,10 +73,63 @@ signame = dict((num, name)
|
|||
# action has to be taken. A BanManager take care of the banned IP
|
||||
# addresses.
|
||||
|
||||
class Action:
|
||||
class ActionBase(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@classmethod
|
||||
def __subclasshook__(cls, C):
|
||||
required = (
|
||||
"getName",
|
||||
"execActionStart",
|
||||
"execActionStop",
|
||||
"execActionBan",
|
||||
"execActionUnban",
|
||||
)
|
||||
for method in required:
|
||||
if not callable(getattr(C, method, None)):
|
||||
return False
|
||||
return True
|
||||
|
||||
def __init__(self, jail, name):
|
||||
self._jail = jail
|
||||
self._name = name
|
||||
self._logSys = logging.getLogger(
|
||||
'%s.%s' % (__name__, self.__class__.__name__))
|
||||
|
||||
@property
|
||||
def jail(self):
|
||||
return self._jail
|
||||
|
||||
@property
|
||||
def logSys(self):
|
||||
return self._logSys
|
||||
|
||||
##
|
||||
# Returns the action name.
|
||||
#
|
||||
# @return the name of the action
|
||||
|
||||
def getName(self):
|
||||
return self._name
|
||||
|
||||
name = property(getName)
|
||||
|
||||
def execActionStart(self):
|
||||
pass
|
||||
|
||||
def execActionBan(self, aInfo):
|
||||
pass
|
||||
|
||||
def execActionUnban(self, aInfo):
|
||||
pass
|
||||
|
||||
def execActionStop(self):
|
||||
pass
|
||||
|
||||
class CommandAction(ActionBase):
|
||||
|
||||
def __init__(self, name):
|
||||
self.__name = name
|
||||
super(CommandAction, self).__init__(None, name)
|
||||
self.__timeout = 60
|
||||
self.__cInfo = dict()
|
||||
## Command executed in order to initialize the system.
|
||||
|
@ -71,22 +144,10 @@ class Action:
|
|||
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
|
||||
|
||||
@classmethod
|
||||
def __subclasshook__(cls, C):
|
||||
return NotImplemented # Standard checks
|
||||
|
||||
##
|
||||
# Sets the timeout period for commands.
|
||||
#
|
||||
|
@ -94,7 +155,7 @@ class Action:
|
|||
|
||||
def setTimeout(self, timeout):
|
||||
self.__timeout = int(timeout)
|
||||
logSys.debug("Set action %s timeout = %i" % (self.__name, timeout))
|
||||
logSys.debug("Set action %s timeout = %i" % (self.getName(), timeout))
|
||||
|
||||
##
|
||||
# Returns the action timeout period for commands.
|
||||
|
@ -160,11 +221,11 @@ class Action:
|
|||
|
||||
def execActionStart(self):
|
||||
if self.__cInfo:
|
||||
if not Action.substituteRecursiveTags(self.__cInfo):
|
||||
if not self.substituteRecursiveTags(self.__cInfo):
|
||||
logSys.error("Cinfo/definitions contain self referencing definitions and cannot be resolved")
|
||||
return False
|
||||
startCmd = Action.replaceTag(self.__actionStart, self.__cInfo)
|
||||
return Action.executeCmd(startCmd, self.__timeout)
|
||||
startCmd = self.replaceTag(self.__actionStart, self.__cInfo)
|
||||
return self.executeCmd(startCmd, self.__timeout)
|
||||
|
||||
##
|
||||
# Set the "ban" command.
|
||||
|
@ -259,8 +320,8 @@ class Action:
|
|||
# @return True if the command succeeded
|
||||
|
||||
def execActionStop(self):
|
||||
stopCmd = Action.replaceTag(self.__actionStop, self.__cInfo)
|
||||
return Action.executeCmd(stopCmd, self.__timeout)
|
||||
stopCmd = self.replaceTag(self.__actionStop, self.__cInfo)
|
||||
return self.executeCmd(stopCmd, self.__timeout)
|
||||
|
||||
##
|
||||
# Sort out tag definitions within other tags
|
||||
|
@ -270,7 +331,7 @@ class Action:
|
|||
# b = <a>_3 b = 3_3
|
||||
# @param tags, a dictionary
|
||||
# @returns tags altered or False if there is a recursive definition
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def substituteRecursiveTags(tags):
|
||||
t = re.compile(r'<([^ >]+)>')
|
||||
for tag, value in tags.iteritems():
|
||||
|
@ -299,15 +360,13 @@ class Action:
|
|||
#logSys.log(5, 'TAG: %s, newvalue: %s' % (tag, value))
|
||||
tags[tag] = value
|
||||
return tags
|
||||
substituteRecursiveTags = staticmethod(substituteRecursiveTags)
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def escapeTag(tag):
|
||||
for c in '\\#&;`|*?~<>^()[]{}$\'"':
|
||||
if c in tag:
|
||||
tag = tag.replace(c, '\\' + c)
|
||||
return tag
|
||||
escapeTag = staticmethod(escapeTag)
|
||||
|
||||
##
|
||||
# Replaces tags in query with property values in aInfo.
|
||||
|
@ -316,25 +375,22 @@ class Action:
|
|||
# @param aInfo the properties
|
||||
# @return a string
|
||||
|
||||
#@staticmethod
|
||||
def replaceTag(query, aInfo):
|
||||
@classmethod
|
||||
def replaceTag(cls, query, aInfo):
|
||||
""" Replace tags in query
|
||||
"""
|
||||
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.
|
||||
|
@ -356,26 +412,26 @@ class Action:
|
|||
logSys.debug("Nothing to do")
|
||||
return True
|
||||
|
||||
checkCmd = Action.replaceTag(self.__actionCheck, self.__cInfo)
|
||||
if not Action.executeCmd(checkCmd, self.__timeout):
|
||||
checkCmd = self.replaceTag(self.__actionCheck, self.__cInfo)
|
||||
if not self.executeCmd(checkCmd, self.__timeout):
|
||||
logSys.error("Invariant check failed. Trying to restore a sane" +
|
||||
" environment")
|
||||
self.execActionStop()
|
||||
self.execActionStart()
|
||||
if not Action.executeCmd(checkCmd, self.__timeout):
|
||||
if not self.executeCmd(checkCmd, self.__timeout):
|
||||
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.__cInfo)
|
||||
|
||||
return Action.executeCmd(realCmd, self.__timeout)
|
||||
return self.executeCmd(realCmd, self.__timeout)
|
||||
|
||||
##
|
||||
# Executes a command.
|
||||
|
@ -389,7 +445,7 @@ class Action:
|
|||
# @param realCmd the command to execute
|
||||
# @return True if the command succeeded
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def executeCmd(realCmd, timeout=60):
|
||||
logSys.debug(realCmd)
|
||||
if not realCmd:
|
||||
|
@ -448,5 +504,4 @@ class Action:
|
|||
logSys.info("HINT on %i: %s"
|
||||
% (retcode, msg % locals()))
|
||||
return False
|
||||
executeCmd = staticmethod(executeCmd)
|
||||
|
||||
|
|
|
@ -24,11 +24,14 @@ __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 fail2ban.server.banmanager import BanManager
|
||||
from fail2ban.server.jailthread import JailThread
|
||||
from fail2ban.server.action import ActionBase, CommandAction, CallingMap
|
||||
from fail2ban.server.mytime import MyTime
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
@ -61,11 +64,24 @@ class Actions(JailThread):
|
|||
#
|
||||
# @param name The action name
|
||||
|
||||
def addAction(self, name):
|
||||
def addAction(self, name, pythonModule=None, initOpts=None):
|
||||
# Check is action name already exists
|
||||
if name in [action.getName() for action in self.__actions]:
|
||||
raise ValueError("Action %s already exists" % name)
|
||||
action = Action(name)
|
||||
if pythonModule is None:
|
||||
action = CommandAction(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 implment required methods" % (
|
||||
pythonModule, customActionModule.Action.__name__))
|
||||
action = customActionModule.Action(self.jail, name, **initOpts)
|
||||
self.__actions.append(action)
|
||||
|
||||
##
|
||||
|
@ -152,7 +168,11 @@ class Actions(JailThread):
|
|||
def run(self):
|
||||
self.setActive(True)
|
||||
for action in self.__actions:
|
||||
action.execActionStart()
|
||||
try:
|
||||
action.execActionStart()
|
||||
except Exception as e:
|
||||
logSys.error("Failed to start jail '%s' action '%s': %s",
|
||||
self.jail.getName(), action.getName(), e)
|
||||
while self._isActive():
|
||||
if not self.getIdle():
|
||||
#logSys.debug(self.jail.getName() + ": action")
|
||||
|
@ -164,7 +184,11 @@ class Actions(JailThread):
|
|||
time.sleep(self.getSleepTime())
|
||||
self.__flushBan()
|
||||
for action in self.__actions:
|
||||
action.execActionStop()
|
||||
try:
|
||||
action.execActionStop()
|
||||
except Exception as e:
|
||||
logSys.error("Failed to stop jail '%s' action '%s': %s",
|
||||
self.jail.getName(), action.getName(), e)
|
||||
logSys.debug(self.jail.getName() + ": action terminated")
|
||||
return True
|
||||
|
||||
|
@ -178,7 +202,7 @@ class Actions(JailThread):
|
|||
def __checkBan(self):
|
||||
ticket = self.jail.getFailTicket()
|
||||
if ticket != False:
|
||||
aInfo = dict()
|
||||
aInfo = CallingMap()
|
||||
bTicket = BanManager.createBanTicket(ticket)
|
||||
aInfo["ip"] = bTicket.getIP()
|
||||
aInfo["failures"] = bTicket.getAttempt()
|
||||
|
@ -200,7 +224,12 @@ class Actions(JailThread):
|
|||
if self.__banManager.addBanTicket(bTicket):
|
||||
logSys.warning("[%s] Ban %s" % (self.jail.getName(), aInfo["ip"]))
|
||||
for action in self.__actions:
|
||||
action.execActionBan(aInfo)
|
||||
try:
|
||||
action.execActionBan(aInfo)
|
||||
except Exception as e:
|
||||
logSys.error(
|
||||
"Failed to execute ban jail '%s' action '%s': %s",
|
||||
self.jail.getName(), action.getName(), e)
|
||||
return True
|
||||
else:
|
||||
logSys.info("[%s] %s already banned" % (self.jail.getName(),
|
||||
|
@ -240,7 +269,12 @@ class Actions(JailThread):
|
|||
aInfo["matches"] = "".join(ticket.getMatches())
|
||||
logSys.warning("[%s] Unban %s" % (self.jail.getName(), aInfo["ip"]))
|
||||
for action in self.__actions:
|
||||
action.execActionUnban(aInfo)
|
||||
try:
|
||||
action.execActionUnban(aInfo)
|
||||
except Exception as e:
|
||||
logSys.error(
|
||||
"Failed to execute unban jail '%s' action '%s': %s",
|
||||
self.jail.getName(), action.getName(), e)
|
||||
|
||||
|
||||
##
|
||||
|
|
|
@ -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 fail2ban.server.ticket import BanTicket
|
||||
from fail2ban.server.mytime import MyTime
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
|
|
@ -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 fail2ban.server.datetemplate import DatePatternRegex, DateTai64n, DateEpoch, DateISO8601
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -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 fail2ban.server.mytime import MyTime
|
||||
from fail2ban.server import iso8601
|
||||
|
||||
import logging
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
|
|
@ -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 fail2ban.server.faildata import FailData
|
||||
from fail2ban.server.ticket import FailTicket
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -21,18 +21,18 @@ __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 fail2ban.server.failmanager import FailManagerEmpty
|
||||
from fail2ban.server.failmanager import FailManager
|
||||
from fail2ban.server.ticket import FailTicket
|
||||
from fail2ban.server.jailthread import JailThread
|
||||
from fail2ban.server.datedetector import DateDetector
|
||||
from fail2ban.server.datetemplate import DatePatternRegex, DateISO8601, DateEpoch, DateTai64n
|
||||
from fail2ban.server.mytime import MyTime
|
||||
from fail2ban.server.failregex import FailRegex, Regex, RegexException
|
||||
from fail2ban.server.action import CommandAction
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
||||
|
@ -378,9 +378,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
|
||||
|
||||
|
|
|
@ -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 fail2ban.server.failmanager import FailManagerEmpty
|
||||
from fail2ban.server.filter import FileFilter
|
||||
from fail2ban.server.mytime import MyTime
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
|
|
@ -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 fail2ban.server.failmanager import FailManagerEmpty
|
||||
from fail2ban.server.filter import FileFilter
|
||||
from fail2ban.server.mytime import MyTime
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -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 fail2ban.server.failmanager import FailManagerEmpty
|
||||
from fail2ban.server.filter import FileFilter
|
||||
from fail2ban.server.mytime import MyTime
|
||||
|
||||
|
||||
if not hasattr(pyinotify, '__version__') \
|
||||
|
|
|
@ -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 fail2ban.server.failmanager import FailManagerEmpty
|
||||
from fail2ban.server.filter import JournalFilter
|
||||
from fail2ban.server.mytime import MyTime
|
||||
|
||||
|
||||
# Gets the instance of the logger.
|
||||
|
|
|
@ -25,7 +25,7 @@ __license__ = "GPL"
|
|||
|
||||
import Queue, logging
|
||||
|
||||
from actions import Actions
|
||||
from fail2ban.server.actions import Actions
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
|
|
@ -21,11 +21,11 @@ __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 fail2ban.exceptions import DuplicateJailException, UnknownJailException
|
||||
from fail2ban.server.jail import Jail
|
||||
|
||||
##
|
||||
# Handles the jails.
|
||||
#
|
||||
|
|
|
@ -25,15 +25,17 @@ __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 fail2ban.server.jails import Jails
|
||||
from fail2ban.server.filter import FileFilter, JournalFilter
|
||||
from fail2ban.server.transmitter import Transmitter
|
||||
from fail2ban.server.asyncserver import AsyncServer
|
||||
from fail2ban.server.asyncserver import AsyncServerException
|
||||
from fail2ban.server.database import Fail2BanDb
|
||||
from fail2ban.server.action import CommandAction
|
||||
from fail2ban import version
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger(__name__)
|
||||
|
||||
|
@ -277,8 +279,8 @@ class Server:
|
|||
return self.__jails.getFilter(name).getMaxLines()
|
||||
|
||||
# Action
|
||||
def addAction(self, name, value):
|
||||
self.__jails.getAction(name).addAction(value)
|
||||
def addAction(self, name, value, *args):
|
||||
self.__jails.getAction(name).addAction(value, *args)
|
||||
|
||||
def getLastAction(self, name):
|
||||
return self.__jails.getAction(name).getLastAction()
|
||||
|
@ -289,14 +291,26 @@ class Server:
|
|||
def delAction(self, name, value):
|
||||
self.__jails.getAction(name).delAction(value)
|
||||
|
||||
def setCInfo(self, name, action, key, value):
|
||||
self.__jails.getAction(name).getAction(action).setCInfo(key, value)
|
||||
def setCInfo(self, name, actionName, key, value):
|
||||
action = self.__jails.getAction(name).getAction(actionName)
|
||||
if isinstance(action, CommandAction):
|
||||
action.setCInfo(key, value)
|
||||
else:
|
||||
raise TypeError("%s is not a CommandAction" % actionName)
|
||||
|
||||
def getCInfo(self, name, action, key):
|
||||
return self.__jails.getAction(name).getAction(action).getCInfo(key)
|
||||
def getCInfo(self, name, actionName, key):
|
||||
action = self.__jails.getAction(name).getAction(actionName)
|
||||
if isinstance(action, CommandAction):
|
||||
return action.getCInfo(key)
|
||||
else:
|
||||
raise TypeError("%s is not a CommandAction" % actionName)
|
||||
|
||||
def delCInfo(self, name, action, key):
|
||||
self.__jails.getAction(name).getAction(action).delCInfo(key)
|
||||
def delCInfo(self, name, actionName, key):
|
||||
action = self.__jails.getAction(name).getAction(actionName)
|
||||
if isinstance(action, CommandAction):
|
||||
action.delCInfo(key)
|
||||
else:
|
||||
raise TypeError("%s is not a CommandAction" % actionName)
|
||||
|
||||
def setBanTime(self, name, value):
|
||||
self.__jails.getAction(name).setBanTime(value)
|
||||
|
@ -310,41 +324,89 @@ class Server:
|
|||
def getBanTime(self, name):
|
||||
return self.__jails.getAction(name).getBanTime()
|
||||
|
||||
def setActionStart(self, name, action, value):
|
||||
self.__jails.getAction(name).getAction(action).setActionStart(value)
|
||||
def setActionStart(self, name, actionName, value):
|
||||
action = self.__jails.getAction(name).getAction(actionName)
|
||||
if isinstance(action, CommandAction):
|
||||
action.setActionStart(value)
|
||||
else:
|
||||
raise TypeError("%s is not a CommandAction" % actionName)
|
||||
|
||||
def getActionStart(self, name, action):
|
||||
return self.__jails.getAction(name).getAction(action).getActionStart()
|
||||
def getActionStart(self, name, actionName):
|
||||
action = self.__jails.getAction(name).getAction(actionName)
|
||||
if isinstance(action, CommandAction):
|
||||
return action.getActionStart()
|
||||
else:
|
||||
raise TypeError("%s is not a CommandAction" % actionName)
|
||||
|
||||
def setActionStop(self, name, action, value):
|
||||
self.__jails.getAction(name).getAction(action).setActionStop(value)
|
||||
def setActionStop(self, name, actionName, value):
|
||||
action = self.__jails.getAction(name).getAction(actionName)
|
||||
if isinstance(action, CommandAction):
|
||||
action.setActionStop(value)
|
||||
else:
|
||||
raise TypeError("%s is not a CommandAction" % actionName)
|
||||
|
||||
def getActionStop(self, name, action):
|
||||
return self.__jails.getAction(name).getAction(action).getActionStop()
|
||||
def getActionStop(self, name, actionName):
|
||||
action = self.__jails.getAction(name).getAction(actionName)
|
||||
if isinstance(action, CommandAction):
|
||||
return action.getActionStop()
|
||||
else:
|
||||
raise TypeError("%s is not a CommandAction" % actionName)
|
||||
|
||||
def setActionCheck(self, name, action, value):
|
||||
self.__jails.getAction(name).getAction(action).setActionCheck(value)
|
||||
def setActionCheck(self, name, actionName, value):
|
||||
action = self.__jails.getAction(name).getAction(actionName)
|
||||
if isinstance(action, CommandAction):
|
||||
action.setActionCheck(value)
|
||||
else:
|
||||
raise TypeError("%s is not a CommandAction" % actionName)
|
||||
|
||||
def getActionCheck(self, name, action):
|
||||
return self.__jails.getAction(name).getAction(action).getActionCheck()
|
||||
def getActionCheck(self, name, actionName):
|
||||
action = self.__jails.getAction(name).getAction(actionName)
|
||||
if isinstance(action, CommandAction):
|
||||
return action.getActionCheck()
|
||||
else:
|
||||
raise TypeError("%s is not a CommandAction" % actionName)
|
||||
|
||||
def setActionBan(self, name, action, value):
|
||||
self.__jails.getAction(name).getAction(action).setActionBan(value)
|
||||
def setActionBan(self, name, actionName, value):
|
||||
action = self.__jails.getAction(name).getAction(actionName)
|
||||
if isinstance(action, CommandAction):
|
||||
action.setActionBan(value)
|
||||
else:
|
||||
raise TypeError("%s is not a CommandAction" % actionName)
|
||||
|
||||
def getActionBan(self, name, action):
|
||||
return self.__jails.getAction(name).getAction(action).getActionBan()
|
||||
def getActionBan(self, name, actionName):
|
||||
action = self.__jails.getAction(name).getAction(actionName)
|
||||
if isinstance(action, CommandAction):
|
||||
return action.getActionBan()
|
||||
else:
|
||||
raise TypeError("%s is not a CommandAction" % actionName)
|
||||
|
||||
def setActionUnban(self, name, action, value):
|
||||
self.__jails.getAction(name).getAction(action).setActionUnban(value)
|
||||
def setActionUnban(self, name, actionName, value):
|
||||
action = self.__jails.getAction(name).getAction(actionName)
|
||||
if isinstance(action, CommandAction):
|
||||
action.setActionUnban(value)
|
||||
else:
|
||||
raise TypeError("%s is not a CommandAction" % actionName)
|
||||
|
||||
def getActionUnban(self, name, action):
|
||||
return self.__jails.getAction(name).getAction(action).getActionUnban()
|
||||
def getActionUnban(self, name, actionName):
|
||||
action = self.__jails.getAction(name).getAction(actionName)
|
||||
if isinstance(action, CommandAction):
|
||||
return action.getActionUnban()
|
||||
else:
|
||||
raise TypeError("%s is not a CommandAction" % actionName)
|
||||
|
||||
def setActionTimeout(self, name, action, value):
|
||||
self.__jails.getAction(name).getAction(action).setTimeout(value)
|
||||
def setActionTimeout(self, name, actionName, value):
|
||||
action = self.__jails.getAction(name).getAction(actionName)
|
||||
if isinstance(action, CommandAction):
|
||||
action.setTimeout(value)
|
||||
else:
|
||||
raise TypeError("%s is not a CommandAction" % actionName)
|
||||
|
||||
def getActionTimeout(self, name, action):
|
||||
return self.__jails.getAction(name).getAction(action).getTimeout()
|
||||
def getActionTimeout(self, name, actionName):
|
||||
action = self.__jails.getAction(name).getAction(actionName)
|
||||
if isinstance(action, CommandAction):
|
||||
return action.getTimeout()
|
||||
else:
|
||||
raise TypeError("%s is not a CommandAction" % actionName)
|
||||
|
||||
# Status
|
||||
def status(self):
|
||||
|
|
|
@ -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__)
|
||||
|
@ -228,8 +229,10 @@ class Transmitter:
|
|||
value = command[2]
|
||||
return self.__server.setUnbanIP(name,value)
|
||||
elif command[1] == "addaction":
|
||||
value = command[2]
|
||||
self.__server.addAction(name, value)
|
||||
args = [command[2]]
|
||||
if len(command) > 3:
|
||||
args.extend([command[3], json.loads(command[4])])
|
||||
self.__server.addAction(name, *args)
|
||||
return self.__server.getLastAction(name).getName()
|
||||
elif command[1] == "delaction":
|
||||
value = command[2]
|
||||
|
|
|
@ -26,18 +26,24 @@ __license__ = "GPL"
|
|||
|
||||
import unittest, time
|
||||
import sys, os, tempfile
|
||||
from fail2ban.server.actions import Actions
|
||||
from dummyjail import DummyJail
|
||||
|
||||
class ExecuteActions(unittest.TestCase):
|
||||
from fail2ban.server.actions import Actions
|
||||
from fail2ban.tests.dummyjail import DummyJail
|
||||
from fail2ban.tests.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):
|
||||
|
@ -77,3 +83,35 @@ class ExecuteActions(unittest.TestCase):
|
|||
self.assertEqual(self.__actions.status(),[("Currently banned", 0 ),
|
||||
("Total banned", 0 ), ("IP list", [] )])
|
||||
|
||||
|
||||
def testAddActionPython(self):
|
||||
self.__actions.addAction(
|
||||
"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.addAction, "Action3", "/does/not/exist.py", {})
|
||||
|
||||
# With optional argument
|
||||
self.__actions.addAction(
|
||||
"Action4", os.path.join(TEST_FILES_DIR, "action.d/action.py"),
|
||||
{'opt1': 'value', 'opt2': 'value2'})
|
||||
# With too many arguments
|
||||
self.assertRaises(
|
||||
TypeError, self.__actions.addAction, "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.addAction, "Action5",
|
||||
os.path.join(TEST_FILES_DIR, "action.d/action.py"), {})
|
||||
|
|
|
@ -27,7 +27,7 @@ __license__ = "GPL"
|
|||
import time
|
||||
import logging, sys
|
||||
|
||||
from fail2ban.server.action import Action
|
||||
from fail2ban.server.action import CommandAction, CallingMap
|
||||
|
||||
from fail2ban.tests.utils import LogCaptureTestCase
|
||||
|
||||
|
@ -35,7 +35,7 @@ class ExecuteAction(LogCaptureTestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""Call before every test case."""
|
||||
self.__action = Action("Test")
|
||||
self.__action = CommandAction("Test")
|
||||
LogCaptureTestCase.setUp(self)
|
||||
|
||||
def tearDown(self):
|
||||
|
@ -43,11 +43,6 @@ class ExecuteAction(LogCaptureTestCase):
|
|||
LogCaptureTestCase.tearDown(self)
|
||||
self.__action.execActionStop()
|
||||
|
||||
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,18 +50,18 @@ 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>'}))
|
||||
# part recursion
|
||||
self.assertFalse(Action.substituteRecursiveTags({'A': 'to=<B> fromip=<IP>', 'C': '<B>', 'B': '<C>', 'D': ''}))
|
||||
self.assertFalse(Action.substituteRecursiveTags({'failregex': 'to=<honeypot> fromip=<IP>', 'sweet': '<honeypot>', 'honeypot': '<sweet>', 'ignoreregex': ''}))
|
||||
self.assertFalse(CommandAction.substituteRecursiveTags({'A': '<A>'}))
|
||||
self.assertFalse(CommandAction.substituteRecursiveTags({'A': '<B>', 'B': '<A>'}))
|
||||
self.assertFalse(CommandAction.substituteRecursiveTags({'A': '<B>', 'B': '<C>', 'C': '<A>'}))
|
||||
# Unresolveable substition
|
||||
self.assertFalse(CommandAction.substituteRecursiveTags({'A': 'to=<B> fromip=<IP>', 'C': '<B>', 'B': '<C>', 'D': ''}))
|
||||
self.assertFalse(CommandAction.substituteRecursiveTags({'failregex': 'to=<honeypot> fromip=<IP>', 'sweet': '<honeypot>', 'honeypot': '<sweet>', 'ignoreregex': ''}))
|
||||
# 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',
|
||||
|
@ -102,15 +97,15 @@ 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")
|
||||
|
@ -172,20 +167,31 @@ class ExecuteAction(LogCaptureTestCase):
|
|||
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
|
||||
CommandAction.executeCmd('sleep 60', timeout=2) # Should take a minute
|
||||
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)
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
|
||||
from fail2ban.server.action import ActionBase
|
||||
|
||||
class TestAction(ActionBase):
|
||||
|
||||
def __init__(self, jail, name, opt1, opt2=None):
|
||||
super(TestAction, self).__init__(jail, name)
|
||||
self.logSys.debug("%s initialised" % self.__class__.__name__)
|
||||
|
||||
def execActionStart(self):
|
||||
self.logSys.debug("%s action start" % self.__class__.__name__)
|
||||
|
||||
def execActionStop(self):
|
||||
self.logSys.debug("%s action stop" % self.__class__.__name__)
|
||||
|
||||
def execActionBan(self, aInfo):
|
||||
self.logSys.debug("%s action ban" % self.__class__.__name__)
|
||||
|
||||
def execActionUnban(self, aInfo):
|
||||
self.logSys.debug("%s action unban" % self.__class__.__name__)
|
||||
|
||||
Action = TestAction
|
|
@ -28,7 +28,7 @@ import shutil
|
|||
|
||||
from glob import glob
|
||||
|
||||
from utils import mbasename, TraceBack, FormatterWithTraceBack
|
||||
from fail2ban.tests.utils import mbasename, TraceBack, FormatterWithTraceBack
|
||||
from fail2ban.helpers import formatExceptionInfo
|
||||
|
||||
class HelpersTest(unittest.TestCase):
|
||||
|
|
|
@ -564,6 +564,23 @@ class Transmitter(TransmitterBase):
|
|||
self.assertEqual(
|
||||
self.transm.proceed(
|
||||
["set", self.jailName, "delaction", "Doesn't exist"])[0],1)
|
||||
self.assertEqual(
|
||||
self.transm.proceed(["set", self.jailName, "addaction", action,
|
||||
os.path.join(TEST_FILES_DIR, "action.d", "action.py"),
|
||||
'{"opt1": "value"}']),
|
||||
(0, action))
|
||||
for cmd, value in zip(cmdList, cmdValueList):
|
||||
self.assertTrue(
|
||||
isinstance(self.transm.proceed(
|
||||
["set", self.jailName, cmd, action, value])[1],
|
||||
TypeError),
|
||||
"set %s for python action did not raise TypeError" % cmd)
|
||||
for cmd, value in zip(cmdList, cmdValueList):
|
||||
self.assertTrue(
|
||||
isinstance(self.transm.proceed(
|
||||
["get", self.jailName, cmd, action])[1],
|
||||
TypeError),
|
||||
"get %s for python action did not raise TypeError" % cmd)
|
||||
|
||||
def testNOK(self):
|
||||
self.assertEqual(self.transm.proceed(["INVALID", "COMMAND"])[0],1)
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue