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.mime.text import MIMEText
|
||||||
from email.utils import formatdate, formataddr
|
from email.utils import formatdate, formataddr
|
||||||
|
|
||||||
from fail2ban.server.actions import ActionBase
|
from fail2ban.server.actions import ActionBase, CallingMap
|
||||||
|
|
||||||
messages = {}
|
messages = {}
|
||||||
messages['start'] = \
|
messages['start'] = \
|
||||||
|
@ -24,14 +24,32 @@ The jail %(jailname)s has been stopped.
|
||||||
Regards,
|
Regards,
|
||||||
Fail2Ban"""
|
Fail2Ban"""
|
||||||
|
|
||||||
messages['ban'] = \
|
messages['ban'] = {}
|
||||||
|
messages['ban']['head'] = \
|
||||||
"""Hi,
|
"""Hi,
|
||||||
|
|
||||||
The IP %(ip)s has just been banned for %(bantime)s seconds
|
The IP %(ip)s has just been banned for %(bantime)s seconds
|
||||||
by Fail2Ban after %(failures)i attempts against %(jailname)s.
|
by Fail2Ban after %(failures)i attempts against %(jailname)s.
|
||||||
|
"""
|
||||||
|
messages['ban']['tail'] = \
|
||||||
|
"""
|
||||||
Regards,
|
Regards,
|
||||||
Fail2Ban"""
|
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):
|
class SMTPAction(ActionBase):
|
||||||
|
|
||||||
|
@ -43,7 +61,7 @@ class SMTPAction(ActionBase):
|
||||||
#TODO: self.ssl = initOpts.get('ssl', "no") == 'yes'
|
#TODO: self.ssl = initOpts.get('ssl', "no") == 'yes'
|
||||||
|
|
||||||
self.user = initOpts.get('user', '')
|
self.user = initOpts.get('user', '')
|
||||||
self.password = initOpts.get('password', None)
|
self.password = initOpts.get('password')
|
||||||
|
|
||||||
self.fromname = initOpts.get('sendername', "Fail2Ban")
|
self.fromname = initOpts.get('sendername', "Fail2Ban")
|
||||||
self.fromaddr = initOpts.get('sender', "fail2ban")
|
self.fromaddr = initOpts.get('sender', "fail2ban")
|
||||||
|
@ -51,6 +69,14 @@ class SMTPAction(ActionBase):
|
||||||
|
|
||||||
self.smtp = smtplib.SMTP()
|
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):
|
def _sendMessage(self, subject, text):
|
||||||
msg = MIMEText(text)
|
msg = MIMEText(text)
|
||||||
msg['Subject'] = subject
|
msg['Subject'] = subject
|
||||||
|
@ -90,14 +116,6 @@ class SMTPAction(ActionBase):
|
||||||
except smtplib.SMTPServerDisconnected:
|
except smtplib.SMTPServerDisconnected:
|
||||||
pass # Not connected
|
pass # Not connected
|
||||||
|
|
||||||
@property
|
|
||||||
def message_values(self):
|
|
||||||
return {
|
|
||||||
'jailname': self.jail.getName(),
|
|
||||||
'hostname': socket.gethostname(),
|
|
||||||
'bantime': self.jail.getAction().getBanTime(),
|
|
||||||
}
|
|
||||||
|
|
||||||
def execActionStart(self):
|
def execActionStart(self):
|
||||||
self._sendMessage(
|
self._sendMessage(
|
||||||
"[Fail2Ban] %(jailname)s: started on %(hostname)s" %
|
"[Fail2Ban] %(jailname)s: started on %(hostname)s" %
|
||||||
|
@ -111,9 +129,15 @@ class SMTPAction(ActionBase):
|
||||||
messages['stop'] % self.message_values)
|
messages['stop'] % self.message_values)
|
||||||
|
|
||||||
def execActionBan(self, aInfo):
|
def execActionBan(self, aInfo):
|
||||||
|
aInfo.update(self.message_values)
|
||||||
|
message = "".join([
|
||||||
|
messages['ban']['head'],
|
||||||
|
messages['ban'].get(self.matches, ""),
|
||||||
|
messages['ban']['tail']
|
||||||
|
])
|
||||||
self._sendMessage(
|
self._sendMessage(
|
||||||
"[Fail2Ban] %(jailname)s: banned %(ip)s from %(hostname)s" %
|
"[Fail2Ban] %(jailname)s: banned %(ip)s from %(hostname)s" %
|
||||||
dict(self.message_values, **aInfo),
|
aInfo,
|
||||||
messages['ban'] % dict(self.message_values, **aInfo))
|
message % aInfo)
|
||||||
|
|
||||||
Action = SMTPAction
|
Action = SMTPAction
|
||||||
|
|
|
@ -24,6 +24,7 @@ __license__ = "GPL"
|
||||||
import logging, os, subprocess, time, signal, tempfile
|
import logging, os, subprocess, time, signal, tempfile
|
||||||
import threading, re
|
import threading, re
|
||||||
from abc import ABCMeta
|
from abc import ABCMeta
|
||||||
|
from collections import MutableMapping
|
||||||
#from subprocess import call
|
#from subprocess import call
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
|
@ -47,6 +48,24 @@ _RETCODE_HINTS = {
|
||||||
signame = dict((num, name)
|
signame = dict((num, name)
|
||||||
for name, num in signal.__dict__.iteritems() if name.startswith("SIG"))
|
for name, num in signal.__dict__.iteritems() if name.startswith("SIG"))
|
||||||
|
|
||||||
|
class CallingMap(MutableMapping):
|
||||||
|
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.
|
# Execute commands.
|
||||||
#
|
#
|
||||||
|
@ -353,11 +372,9 @@ class CommandAction(ActionBase):
|
||||||
""" Replace tags in query
|
""" Replace tags in query
|
||||||
"""
|
"""
|
||||||
string = query
|
string = query
|
||||||
for tag, value in aInfo.iteritems():
|
for tag in aInfo:
|
||||||
if "<%s>" % tag in query:
|
if "<%s>" % tag in query:
|
||||||
if callable(value):
|
value = str(aInfo[tag]) # assure string
|
||||||
value = value()
|
|
||||||
value = str(value) # assure string
|
|
||||||
if tag.endswith('matches'):
|
if tag.endswith('matches'):
|
||||||
# That one needs to be escaped since its content is
|
# That one needs to be escaped since its content is
|
||||||
# out of our control
|
# out of our control
|
||||||
|
|
|
@ -30,7 +30,7 @@ import imp
|
||||||
|
|
||||||
from fail2ban.server.banmanager import BanManager
|
from fail2ban.server.banmanager import BanManager
|
||||||
from fail2ban.server.jailthread import JailThread
|
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
|
from fail2ban.server.mytime import MyTime
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
|
@ -202,7 +202,7 @@ class Actions(JailThread):
|
||||||
def __checkBan(self):
|
def __checkBan(self):
|
||||||
ticket = self.jail.getFailTicket()
|
ticket = self.jail.getFailTicket()
|
||||||
if ticket != False:
|
if ticket != False:
|
||||||
aInfo = dict()
|
aInfo = CallingMap()
|
||||||
bTicket = BanManager.createBanTicket(ticket)
|
bTicket = BanManager.createBanTicket(ticket)
|
||||||
aInfo["ip"] = bTicket.getIP()
|
aInfo["ip"] = bTicket.getIP()
|
||||||
aInfo["failures"] = bTicket.getAttempt()
|
aInfo["failures"] = bTicket.getAttempt()
|
||||||
|
|
|
@ -27,7 +27,7 @@ __license__ = "GPL"
|
||||||
import time
|
import time
|
||||||
import logging, sys
|
import logging, sys
|
||||||
|
|
||||||
from fail2ban.server.action import CommandAction
|
from fail2ban.server.action import CommandAction, CallingMap
|
||||||
|
|
||||||
from fail2ban.tests.utils import LogCaptureTestCase
|
from fail2ban.tests.utils import LogCaptureTestCase
|
||||||
|
|
||||||
|
@ -94,15 +94,15 @@ class ExecuteAction(LogCaptureTestCase):
|
||||||
|
|
||||||
# Callable
|
# Callable
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.__action.replaceTag("09 <callable> 11",
|
self.__action.replaceTag("09 <callme> 11",
|
||||||
{'callable': lambda: str(10)}),
|
CallingMap(callme=lambda: str(10))),
|
||||||
"09 10 11")
|
"09 10 11")
|
||||||
|
|
||||||
# As tag not present, therefore callable should not be called
|
# As tag not present, therefore callable should not be called
|
||||||
# Will raise ValueError if it is
|
# Will raise ValueError if it is
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.__action.replaceTag("abc",
|
self.__action.replaceTag("abc",
|
||||||
{'callable': lambda: int("a")}), "abc")
|
CallingMap(callme=lambda: int("a"))), "abc")
|
||||||
|
|
||||||
def testExecuteActionBan(self):
|
def testExecuteActionBan(self):
|
||||||
self.__action.setActionStart("touch /tmp/fail2ban.test")
|
self.__action.setActionStart("touch /tmp/fail2ban.test")
|
||||||
|
@ -181,3 +181,11 @@ class ExecuteAction(LogCaptureTestCase):
|
||||||
'echo "The rain in Spain stays mainly in the plain" 1>&2')
|
'echo "The rain in Spain stays mainly in the plain" 1>&2')
|
||||||
self.assertTrue(self._is_logged(
|
self.assertTrue(self._is_logged(
|
||||||
"'The rain in Spain stays mainly in the plain\\n'"))
|
"'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