|
|
|
@ -18,7 +18,6 @@
|
|
|
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
|
|
|
|
|
|
|
|
import os |
|
|
|
|
import smtpd |
|
|
|
|
import threading |
|
|
|
|
import unittest |
|
|
|
|
import re |
|
|
|
@ -29,134 +28,139 @@ else:
|
|
|
|
|
import imp |
|
|
|
|
|
|
|
|
|
from ..dummyjail import DummyJail |
|
|
|
|
|
|
|
|
|
from ..utils import CONFIG_DIR, asyncserver, Utils, uni_decode |
|
|
|
|
|
|
|
|
|
class TestSMTPServer(smtpd.SMTPServer): |
|
|
|
|
|
|
|
|
|
def __init__(self, *args): |
|
|
|
|
smtpd.SMTPServer.__init__(self, *args) |
|
|
|
|
self.ready = False |
|
|
|
|
|
|
|
|
|
def process_message(self, peer, mailfrom, rcpttos, data, **kwargs): |
|
|
|
|
self.peer = peer |
|
|
|
|
self.mailfrom = mailfrom |
|
|
|
|
self.rcpttos = rcpttos |
|
|
|
|
self.org_data = data |
|
|
|
|
# replace new line (with tab or space) for possible mime translations (word wrap), |
|
|
|
|
self.data = re.sub(r"\n[\t ]", " ", uni_decode(data)) |
|
|
|
|
self.ready = True |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SMTPActionTest(unittest.TestCase): |
|
|
|
|
|
|
|
|
|
def setUp(self): |
|
|
|
|
"""Call before every test case.""" |
|
|
|
|
unittest.F2B.SkipIfCfgMissing(action='smtp.py') |
|
|
|
|
super(SMTPActionTest, self).setUp() |
|
|
|
|
self.jail = DummyJail() |
|
|
|
|
pythonModule = os.path.join(CONFIG_DIR, "action.d", "smtp.py") |
|
|
|
|
pythonModuleName = os.path.basename(pythonModule.rstrip(".py")) |
|
|
|
|
if sys.version_info >= (3, 3): |
|
|
|
|
customActionModule = importlib.machinery.SourceFileLoader( |
|
|
|
|
pythonModuleName, pythonModule).load_module() |
|
|
|
|
else: |
|
|
|
|
customActionModule = imp.load_source( |
|
|
|
|
pythonModuleName, pythonModule) |
|
|
|
|
|
|
|
|
|
self.smtpd = TestSMTPServer(("localhost", 0), None) |
|
|
|
|
port = self.smtpd.socket.getsockname()[1] |
|
|
|
|
|
|
|
|
|
self.action = customActionModule.Action( |
|
|
|
|
self.jail, "test", host="localhost:%i" % port) |
|
|
|
|
|
|
|
|
|
## because of bug in loop (see loop in asyncserver.py) use it's loop instead of asyncore.loop: |
|
|
|
|
self._active = True |
|
|
|
|
self._loop_thread = threading.Thread( |
|
|
|
|
target=asyncserver.loop, kwargs={'active': lambda: self._active}) |
|
|
|
|
self._loop_thread.daemon = True |
|
|
|
|
self._loop_thread.start() |
|
|
|
|
|
|
|
|
|
def tearDown(self): |
|
|
|
|
"""Call after every test case.""" |
|
|
|
|
self.smtpd.close() |
|
|
|
|
self._active = False |
|
|
|
|
self._loop_thread.join() |
|
|
|
|
super(SMTPActionTest, self).tearDown() |
|
|
|
|
|
|
|
|
|
def _exec_and_wait(self, doaction, timeout=3, short=False): |
|
|
|
|
if short: timeout /= 25 |
|
|
|
|
self.smtpd.ready = False |
|
|
|
|
doaction() |
|
|
|
|
Utils.wait_for(lambda: self.smtpd.ready, timeout) |
|
|
|
|
|
|
|
|
|
def testStart(self): |
|
|
|
|
self._exec_and_wait(self.action.start) |
|
|
|
|
self.assertEqual(self.smtpd.mailfrom, "fail2ban") |
|
|
|
|
self.assertEqual(self.smtpd.rcpttos, ["root"]) |
|
|
|
|
self.assertTrue( |
|
|
|
|
"Subject: [Fail2Ban] %s: started" % self.jail.name |
|
|
|
|
in self.smtpd.data) |
|
|
|
|
|
|
|
|
|
def testStop(self): |
|
|
|
|
self._exec_and_wait(self.action.stop) |
|
|
|
|
self.assertEqual(self.smtpd.mailfrom, "fail2ban") |
|
|
|
|
self.assertEqual(self.smtpd.rcpttos, ["root"]) |
|
|
|
|
self.assertTrue( |
|
|
|
|
"Subject: [Fail2Ban] %s: stopped" % |
|
|
|
|
self.jail.name in self.smtpd.data) |
|
|
|
|
|
|
|
|
|
def _testBan(self, restored=False): |
|
|
|
|
aInfo = { |
|
|
|
|
'ip': "127.0.0.2", |
|
|
|
|
'failures': 3, |
|
|
|
|
'matches': "Test fail 1\n", |
|
|
|
|
'ipjailmatches': "Test fail 1\nTest Fail2\n", |
|
|
|
|
'ipmatches': "Test fail 1\nTest Fail2\nTest Fail3\n", |
|
|
|
|
} |
|
|
|
|
if restored: |
|
|
|
|
aInfo['restored'] = 1 |
|
|
|
|
|
|
|
|
|
self._exec_and_wait(lambda: self.action.ban(aInfo), short=restored) |
|
|
|
|
if restored: # no mail, should raises attribute error: |
|
|
|
|
self.assertRaises(AttributeError, lambda: self.smtpd.mailfrom) |
|
|
|
|
return |
|
|
|
|
self.assertEqual(self.smtpd.mailfrom, "fail2ban") |
|
|
|
|
self.assertEqual(self.smtpd.rcpttos, ["root"]) |
|
|
|
|
subject = "Subject: [Fail2Ban] %s: banned %s" % ( |
|
|
|
|
self.jail.name, aInfo['ip']) |
|
|
|
|
self.assertIn(subject, self.smtpd.data) |
|
|
|
|
self.assertIn( |
|
|
|
|
"%i attempts" % aInfo['failures'], self.smtpd.data) |
|
|
|
|
|
|
|
|
|
self.action.matches = "matches" |
|
|
|
|
self._exec_and_wait(lambda: self.action.ban(aInfo)) |
|
|
|
|
self.assertIn(aInfo['matches'], self.smtpd.data) |
|
|
|
|
|
|
|
|
|
self.action.matches = "ipjailmatches" |
|
|
|
|
self._exec_and_wait(lambda: self.action.ban(aInfo)) |
|
|
|
|
self.assertIn(aInfo['ipjailmatches'], self.smtpd.data) |
|
|
|
|
|
|
|
|
|
self.action.matches = "ipmatches" |
|
|
|
|
self._exec_and_wait(lambda: self.action.ban(aInfo)) |
|
|
|
|
self.assertIn(aInfo['ipmatches'], self.smtpd.data) |
|
|
|
|
|
|
|
|
|
def testBan(self): |
|
|
|
|
self._testBan() |
|
|
|
|
|
|
|
|
|
def testNOPByRestored(self): |
|
|
|
|
self._testBan(restored=True) |
|
|
|
|
|
|
|
|
|
def testOptions(self): |
|
|
|
|
self._exec_and_wait(self.action.start) |
|
|
|
|
self.assertEqual(self.smtpd.mailfrom, "fail2ban") |
|
|
|
|
self.assertEqual(self.smtpd.rcpttos, ["root"]) |
|
|
|
|
|
|
|
|
|
self.action.fromname = "Test" |
|
|
|
|
self.action.fromaddr = "test@example.com" |
|
|
|
|
self.action.toaddr = "test@example.com, test2@example.com" |
|
|
|
|
self._exec_and_wait(self.action.start) |
|
|
|
|
self.assertEqual(self.smtpd.mailfrom, "test@example.com") |
|
|
|
|
self.assertTrue("From: %s <%s>" % |
|
|
|
|
(self.action.fromname, self.action.fromaddr) in self.smtpd.data) |
|
|
|
|
self.assertEqual(set(self.smtpd.rcpttos), set(["test@example.com", "test2@example.com"])) |
|
|
|
|
try: |
|
|
|
|
import smtpd |
|
|
|
|
|
|
|
|
|
class TestSMTPServer(smtpd.SMTPServer): |
|
|
|
|
|
|
|
|
|
def __init__(self, *args): |
|
|
|
|
smtpd.SMTPServer.__init__(self, *args) |
|
|
|
|
self.ready = False |
|
|
|
|
|
|
|
|
|
def process_message(self, peer, mailfrom, rcpttos, data, **kwargs): |
|
|
|
|
self.peer = peer |
|
|
|
|
self.mailfrom = mailfrom |
|
|
|
|
self.rcpttos = rcpttos |
|
|
|
|
self.org_data = data |
|
|
|
|
# replace new line (with tab or space) for possible mime translations (word wrap), |
|
|
|
|
self.data = re.sub(r"\n[\t ]", " ", uni_decode(data)) |
|
|
|
|
self.ready = True |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SMTPActionTest(unittest.TestCase): |
|
|
|
|
|
|
|
|
|
def setUp(self): |
|
|
|
|
"""Call before every test case.""" |
|
|
|
|
unittest.F2B.SkipIfCfgMissing(action='smtp.py') |
|
|
|
|
super(SMTPActionTest, self).setUp() |
|
|
|
|
self.jail = DummyJail() |
|
|
|
|
pythonModule = os.path.join(CONFIG_DIR, "action.d", "smtp.py") |
|
|
|
|
pythonModuleName = os.path.basename(pythonModule.rstrip(".py")) |
|
|
|
|
if sys.version_info >= (3, 3): |
|
|
|
|
customActionModule = importlib.machinery.SourceFileLoader( |
|
|
|
|
pythonModuleName, pythonModule).load_module() |
|
|
|
|
else: |
|
|
|
|
customActionModule = imp.load_source( |
|
|
|
|
pythonModuleName, pythonModule) |
|
|
|
|
|
|
|
|
|
self.smtpd = TestSMTPServer(("localhost", 0), None) |
|
|
|
|
port = self.smtpd.socket.getsockname()[1] |
|
|
|
|
|
|
|
|
|
self.action = customActionModule.Action( |
|
|
|
|
self.jail, "test", host="localhost:%i" % port) |
|
|
|
|
|
|
|
|
|
## because of bug in loop (see loop in asyncserver.py) use it's loop instead of asyncore.loop: |
|
|
|
|
self._active = True |
|
|
|
|
self._loop_thread = threading.Thread( |
|
|
|
|
target=asyncserver.loop, kwargs={'active': lambda: self._active}) |
|
|
|
|
self._loop_thread.daemon = True |
|
|
|
|
self._loop_thread.start() |
|
|
|
|
|
|
|
|
|
def tearDown(self): |
|
|
|
|
"""Call after every test case.""" |
|
|
|
|
self.smtpd.close() |
|
|
|
|
self._active = False |
|
|
|
|
self._loop_thread.join() |
|
|
|
|
super(SMTPActionTest, self).tearDown() |
|
|
|
|
|
|
|
|
|
def _exec_and_wait(self, doaction, timeout=3, short=False): |
|
|
|
|
if short: timeout /= 25 |
|
|
|
|
self.smtpd.ready = False |
|
|
|
|
doaction() |
|
|
|
|
Utils.wait_for(lambda: self.smtpd.ready, timeout) |
|
|
|
|
|
|
|
|
|
def testStart(self): |
|
|
|
|
self._exec_and_wait(self.action.start) |
|
|
|
|
self.assertEqual(self.smtpd.mailfrom, "fail2ban") |
|
|
|
|
self.assertEqual(self.smtpd.rcpttos, ["root"]) |
|
|
|
|
self.assertTrue( |
|
|
|
|
"Subject: [Fail2Ban] %s: started" % self.jail.name |
|
|
|
|
in self.smtpd.data) |
|
|
|
|
|
|
|
|
|
def testStop(self): |
|
|
|
|
self._exec_and_wait(self.action.stop) |
|
|
|
|
self.assertEqual(self.smtpd.mailfrom, "fail2ban") |
|
|
|
|
self.assertEqual(self.smtpd.rcpttos, ["root"]) |
|
|
|
|
self.assertTrue( |
|
|
|
|
"Subject: [Fail2Ban] %s: stopped" % |
|
|
|
|
self.jail.name in self.smtpd.data) |
|
|
|
|
|
|
|
|
|
def _testBan(self, restored=False): |
|
|
|
|
aInfo = { |
|
|
|
|
'ip': "127.0.0.2", |
|
|
|
|
'failures': 3, |
|
|
|
|
'matches': "Test fail 1\n", |
|
|
|
|
'ipjailmatches': "Test fail 1\nTest Fail2\n", |
|
|
|
|
'ipmatches': "Test fail 1\nTest Fail2\nTest Fail3\n", |
|
|
|
|
} |
|
|
|
|
if restored: |
|
|
|
|
aInfo['restored'] = 1 |
|
|
|
|
|
|
|
|
|
self._exec_and_wait(lambda: self.action.ban(aInfo), short=restored) |
|
|
|
|
if restored: # no mail, should raises attribute error: |
|
|
|
|
self.assertRaises(AttributeError, lambda: self.smtpd.mailfrom) |
|
|
|
|
return |
|
|
|
|
self.assertEqual(self.smtpd.mailfrom, "fail2ban") |
|
|
|
|
self.assertEqual(self.smtpd.rcpttos, ["root"]) |
|
|
|
|
subject = "Subject: [Fail2Ban] %s: banned %s" % ( |
|
|
|
|
self.jail.name, aInfo['ip']) |
|
|
|
|
self.assertIn(subject, self.smtpd.data) |
|
|
|
|
self.assertIn( |
|
|
|
|
"%i attempts" % aInfo['failures'], self.smtpd.data) |
|
|
|
|
|
|
|
|
|
self.action.matches = "matches" |
|
|
|
|
self._exec_and_wait(lambda: self.action.ban(aInfo)) |
|
|
|
|
self.assertIn(aInfo['matches'], self.smtpd.data) |
|
|
|
|
|
|
|
|
|
self.action.matches = "ipjailmatches" |
|
|
|
|
self._exec_and_wait(lambda: self.action.ban(aInfo)) |
|
|
|
|
self.assertIn(aInfo['ipjailmatches'], self.smtpd.data) |
|
|
|
|
|
|
|
|
|
self.action.matches = "ipmatches" |
|
|
|
|
self._exec_and_wait(lambda: self.action.ban(aInfo)) |
|
|
|
|
self.assertIn(aInfo['ipmatches'], self.smtpd.data) |
|
|
|
|
|
|
|
|
|
def testBan(self): |
|
|
|
|
self._testBan() |
|
|
|
|
|
|
|
|
|
def testNOPByRestored(self): |
|
|
|
|
self._testBan(restored=True) |
|
|
|
|
|
|
|
|
|
def testOptions(self): |
|
|
|
|
self._exec_and_wait(self.action.start) |
|
|
|
|
self.assertEqual(self.smtpd.mailfrom, "fail2ban") |
|
|
|
|
self.assertEqual(self.smtpd.rcpttos, ["root"]) |
|
|
|
|
|
|
|
|
|
self.action.fromname = "Test" |
|
|
|
|
self.action.fromaddr = "test@example.com" |
|
|
|
|
self.action.toaddr = "test@example.com, test2@example.com" |
|
|
|
|
self._exec_and_wait(self.action.start) |
|
|
|
|
self.assertEqual(self.smtpd.mailfrom, "test@example.com") |
|
|
|
|
self.assertTrue("From: %s <%s>" % |
|
|
|
|
(self.action.fromname, self.action.fromaddr) in self.smtpd.data) |
|
|
|
|
self.assertEqual(set(self.smtpd.rcpttos), set(["test@example.com", "test2@example.com"])) |
|
|
|
|
|
|
|
|
|
except ImportError as e: |
|
|
|
|
print("I: Skipping smtp tests: %s" % e) |
|
|
|
|