mirror of https://github.com/fail2ban/fail2ban
ENH: Add matches to smtp.py action
parent
f37c90cdba
commit
6ef911185d
|
@ -5,7 +5,7 @@ import smtplib
|
|||
from email.mime.text import MIMEText
|
||||
from email.utils import formatdate, formataddr
|
||||
|
||||
from fail2ban.server.actions import ActionBase
|
||||
from fail2ban.server.actions import ActionBase, CallingMap
|
||||
|
||||
messages = {}
|
||||
messages['start'] = \
|
||||
|
@ -24,14 +24,32 @@ The jail %(jailname)s has been stopped.
|
|||
Regards,
|
||||
Fail2Ban"""
|
||||
|
||||
messages['ban'] = \
|
||||
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):
|
||||
|
||||
|
@ -43,7 +61,7 @@ class SMTPAction(ActionBase):
|
|||
#TODO: self.ssl = initOpts.get('ssl', "no") == 'yes'
|
||||
|
||||
self.user = initOpts.get('user', '')
|
||||
self.password = initOpts.get('password', None)
|
||||
self.password = initOpts.get('password')
|
||||
|
||||
self.fromname = initOpts.get('sendername', "Fail2Ban")
|
||||
self.fromaddr = initOpts.get('sender', "fail2ban")
|
||||
|
@ -51,6 +69,14 @@ class SMTPAction(ActionBase):
|
|||
|
||||
self.smtp = smtplib.SMTP()
|
||||
|
||||
self.matches = initOpts.get('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
|
||||
|
@ -90,14 +116,6 @@ class SMTPAction(ActionBase):
|
|||
except smtplib.SMTPServerDisconnected:
|
||||
pass # Not connected
|
||||
|
||||
@property
|
||||
def message_values(self):
|
||||
return {
|
||||
'jailname': self.jail.getName(),
|
||||
'hostname': socket.gethostname(),
|
||||
'bantime': self.jail.getAction().getBanTime(),
|
||||
}
|
||||
|
||||
def execActionStart(self):
|
||||
self._sendMessage(
|
||||
"[Fail2Ban] %(jailname)s: started on %(hostname)s" %
|
||||
|
@ -111,9 +129,15 @@ class SMTPAction(ActionBase):
|
|||
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" %
|
||||
dict(self.message_values, **aInfo),
|
||||
messages['ban'] % dict(self.message_values, **aInfo))
|
||||
aInfo,
|
||||
message % aInfo)
|
||||
|
||||
Action = SMTPAction
|
||||
|
|
|
@ -24,6 +24,7 @@ __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.
|
||||
|
@ -47,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.
|
||||
#
|
||||
|
@ -353,11 +372,9 @@ class CommandAction(ActionBase):
|
|||
""" 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
|
||||
|
|
|
@ -30,7 +30,7 @@ import imp
|
|||
|
||||
from fail2ban.server.banmanager import BanManager
|
||||
from fail2ban.server.jailthread import JailThread
|
||||
from fail2ban.server.action import ActionBase, CommandAction
|
||||
from fail2ban.server.action import ActionBase, CommandAction, CallingMap
|
||||
from fail2ban.server.mytime import MyTime
|
||||
|
||||
# Gets the instance of the logger.
|
||||
|
@ -202,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()
|
||||
|
|
|
@ -27,7 +27,7 @@ __license__ = "GPL"
|
|||
import time
|
||||
import logging, sys
|
||||
|
||||
from fail2ban.server.action import CommandAction
|
||||
from fail2ban.server.action import CommandAction, CallingMap
|
||||
|
||||
from fail2ban.tests.utils import LogCaptureTestCase
|
||||
|
||||
|
@ -94,15 +94,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")
|
||||
|
@ -181,3 +181,11 @@ class ExecuteAction(LogCaptureTestCase):
|
|||
'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'))
|
||||
|
||||
# Should work fine
|
||||
self.assertEqual("%(callme)s okay" % mymap, "10 okay")
|
||||
# Error will now trip, demonstrating delayed call
|
||||
self.assertRaises(ValueError, lambda x: "%(error)i" % x, mymap)
|
||||
|
|
Loading…
Reference in New Issue