Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 59 additions & 37 deletions emails/testsuite/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,59 +40,81 @@ def stop(self):
self._process.terminate()


class TestLamsonSmtpServer:
class SecureSMTPDServer(object):

def __init__(self):
import sys
sys.path.insert(0, os.path.dirname(__file__))
import lamsondebuggingsmtpinstance
import lamsondebuggingsmtpinstance.config.settings
self.lamsondir = os.path.dirname(lamsondebuggingsmtpinstance.__file__)
settings = lamsondebuggingsmtpinstance.config.settings
self.host = settings.receiver_config['host']
self.port = settings.receiver_config['port']
self.lock = threading.Lock()
self._started = False


def _lamson_command(self, lamson_params):
r = subprocess.call("lamson {0}".format( lamson_params ), shell=True, cwd=self.lamsondir)
print("_lamson_command '{0}' return code is {1}".format(lamson_params, r))
self._cwd = os.path.join(os.path.dirname(__file__), 'contrib/local-smtpd')
self._process = None
self.host = 'localhost'
self.user = 'A'
self.password = 'B'
self.argv = None

def as_dict(self):
r = {'host': self.host, 'port': self.port, 'fail_silently': False, 'debug': 1}
argv = self.argv or []
if 'ssl' in argv:
r['ssl'] = True
if 'auth' in argv:
r.update({'user': self.user, 'password': self.password})
return r

def _start_lamson(self):
if not self._started:
self._stop_lamson() # just is case
logger.debug('stop lamson...')
return self._lamson_command('start -FORCE')

def _stop_lamson(self):
return self._lamson_command('stop')

def get_server(self):
self._start_lamson()
time.sleep(1)
def get_server(self, argv=None):
if self._process is None:
self.argv = argv or []
if 'ssl' in self.argv:
self.port = 25126
elif 'auth' in self.argv:
self.port = 25127
else:
self.port = 25125
cmd = '/bin/sh ./run.sh'.split(' ')
if argv:
cmd.extend(argv)
self._process = subprocess.Popen(cmd, shell=False, cwd=self._cwd)
logger.error('Started test smtp server "%s", pid: %s', cmd, self._process.pid)
#print('Started test smtp server "{0}", pid: {1}'.format(CMD, self._process.pid))
time.sleep(1)
return self

def stop(self):
if self._started:
logger.debug('stop lamson...')
self._start_lamson()
if self._process:
logger.error('kill process...')
self._process.terminate()
time.sleep(1)


@pytest.fixture(scope="module")
def smtp_server(request):
logger.debug('smtp_server...')
try:
import lamson
ext_server = TestLamsonSmtpServer()
except ImportError:
ext_server = TestSmtpServer()
ext_server = SecureSMTPDServer()
def fin():
print ("stopping ext_server")
ext_server.stop()
request.addfinalizer(fin)
return ext_server.get_server() #host, ext_server.port)
return ext_server.get_server()

@pytest.fixture(scope="module")
def smtp_server_with_auth(request):
logger.debug('smtp_server with auth...')
ext_server = SecureSMTPDServer()
def fin():
print ("stopping ext_server with auth")
ext_server.stop()
request.addfinalizer(fin)
return ext_server.get_server(['auth'])


@pytest.fixture(scope="module")
def smtp_server_with_ssl(request):
logger.debug('smtp_server with ssl...')
ext_server = SecureSMTPDServer()
def fin():
print ("stopping ext_server with auth")
ext_server.stop()
request.addfinalizer(fin)
return ext_server.get_server(['ssl'])


@pytest.fixture(scope='module')
def django_email_backend(request):
Expand Down
17 changes: 17 additions & 0 deletions emails/testsuite/contrib/local-smtpd/example.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICsjCCAhugAwIBAgIJAPJ/FysSCcu+MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwIBcNMTUwMjI1MjM1NDAyWhgPMjExNTAyMDEyMzU0MDJa
MEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJ
bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ
AoGBAKfPd+Qa+/z1phbnV2sivOIfkd+wvroTmthnIaMDcun1DBCfyNw8byvBIwZv
Z0Fvco+zb4eBs3ZBHsiLYi9WXdU2NmYr0mcKauDOJW0lE7eOzCUP4Bq2XBgVqf6x
TXTNUaFrwZkxTA221NsVRqK3fytcXBi3a3zIvYCcvyCjNPiPAgMBAAGjgacwgaQw
HQYDVR0OBBYEFESV2wfHVrhtIwlGDQ7IXRKZpZoeMHUGA1UdIwRuMGyAFESV2wfH
VrhtIwlGDQ7IXRKZpZoeoUmkRzBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29t
ZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkggkA8n8X
KxIJy74wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCgZOIVvCdM4sY+
OKLkxWEZr2zztafE9nHV77VXYMp8u3IOo2Iz4Ygn60SxzHEpWUUo44M9/1i7vkDQ
P0o/kPhzEVj9Fnx51YPqOakB+r9e25Enti2WkGdD0TUPC5IthcQQk0dnzwQG24mp
gfhg8GIWdqmopRbcW3DWgSqh45sB8g==
-----END CERTIFICATE-----
15 changes: 15 additions & 0 deletions emails/testsuite/contrib/local-smtpd/example.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQCnz3fkGvv89aYW51drIrziH5HfsL66E5rYZyGjA3Lp9QwQn8jc
PG8rwSMGb2dBb3KPs2+HgbN2QR7Ii2IvVl3VNjZmK9JnCmrgziVtJRO3jswlD+Aa
tlwYFan+sU10zVGha8GZMUwNttTbFUait38rXFwYt2t8yL2AnL8gozT4jwIDAQAB
AoGAcfo/Y0ZUuxaaDdppjNIWWru4l6dzk+028h7yQMdZ6MBQxoXQpo3BsIVI5dkK
1+37cNEeQnp8yygl4W6SbLaLmehakK485s8hGD/ZgPS+CJmyjF2jr6BSzKVEjYtH
7+eMKBgZ3MjRC8Rr+QjnUZa65Pd6YVShqWcqiSjFQic4aEECQQDcNnTdPLXkaQwR
pXFuP70arH257vXUzaLi0c+I1UXgUR++KGA4yhJWZCp86Ik7R2mkHvgBdnO7KW3B
8LXbdX9vAkEAwxTpONYcMDRXSwOrAFyuQimpBoTBu0e/c3gA37tvmaaIPpK/V4pV
wMXpxgv4eTUJxf/N+ZcqTSJt8fDSIBOI4QJBANhcrO/eQXyc9Z205sEC4QL/LTxt
G54tOPgQWw8/NLuUGVMViozhhajaG6DEPGlA3fvB7bxKLKVcrBlcLuHkDQMCQQCV
EL99fK4hb31chrr+FdPaHrdXkc3va02xz/rq+vC1+fiVx9CJ9dy85v5RJQiCpbKI
J4WeuJHMSwi0HQ6TEBpBAkAPIphfofNE8lkIpJ1ocG7687zsmZDXJdzG4RToW4Un
UxHn0Mb3b39vkKIwr47W32fSyuZ7rG7YF8D1tR1B2oPN
-----END RSA PRIVATE KEY-----
37 changes: 37 additions & 0 deletions emails/testsuite/contrib/local-smtpd/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# encoding: utf-8
import logging
from secure_smtpd import SMTPServer, LOG_NAME
import sys

class SSLSMTPServer(SMTPServer):
def process_message(self, peer, mailfrom, rcpttos, message_data):
print(message_data)
with open('secure-smtpd.log', 'a') as f:
f.write(message_data)
f.write('\n\n')

class MyCredentialValidator(object):
def validate(self, username, password):
if username == 'A' and password == 'B':
return True
return False

logger = logging.getLogger(LOG_NAME)
logger.setLevel(logging.INFO)

params = {}
port = 25125

if 'auth' in sys.argv:
params.update({'require_authentication': True, 'credential_validator': MyCredentialValidator()})
port = 25127

if 'ssl' in sys.argv:
params.update({'ssl': True, 'certfile': 'example.crt', 'keyfile': 'example.key'})
port = 25126

if 'timeout':
params.update({'maximum_execution_time': 10.0})

server = SSLSMTPServer(('127.0.0.1', port), None, **params)
server.run()
9 changes: 9 additions & 0 deletions emails/testsuite/contrib/local-smtpd/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/sh
# Looks like python3 has a bug in smtpd (SMTPSenderRefused: (503, 'Error: send HELO first'...)
# So we try to start python2

PYTHON=python2.7
# next line doesn't works actually. TODO: fix it
(which python2.7 && export PYTHON=python2.7) || (which python2.6 && export PYTHON=python2.6) || export PYTHON=python
echo "$PYTHON run.py $@"
$PYTHON run.py $@
15 changes: 15 additions & 0 deletions emails/testsuite/contrib/local-smtpd/secure_smtpd/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

Copyright (c) 2014, Benjamin Coe <bencoe@gmail.com>

Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

4 changes: 4 additions & 0 deletions emails/testsuite/contrib/local-smtpd/secure_smtpd/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import config
from .config import LOG_NAME
from .smtp_server import SMTPServer
from .proxy_server import ProxyServer
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import log
from .log import LOG_NAME
33 changes: 33 additions & 0 deletions emails/testsuite/contrib/local-smtpd/secure_smtpd/config/log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import logging, sys
from logging.handlers import RotatingFileHandler
from logging import StreamHandler

LOG_NAME = 'secure-smtpd'

class Log(object):

def __init__(self, log_name):
self.log_name = log_name
self.logger = logging.getLogger( self.log_name )
self._remove_handlers()
self._add_handler()
self.logger.setLevel(logging.DEBUG)

def _remove_handlers(self):
for handler in self.logger.handlers:
self.logger.removeHandler(handler)

def _add_handler(self):
try:
handler = RotatingFileHandler(
'/var/log/%s.log' % self.log_name,
maxBytes=10485760,
backupCount=3
)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
self.logger.addHandler(handler)
except IOError:
self.logger.addHandler(StreamHandler(sys.stderr))

Log(LOG_NAME)
16 changes: 16 additions & 0 deletions emails/testsuite/contrib/local-smtpd/secure_smtpd/process_pool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import time
from multiprocessing import Process, Queue

class ProcessPool(object):

def __init__(self, func, process_count=5):
self.func = func
self.process_count = process_count
self.queue = Queue()
self._create_processes()

def _create_processes(self):
for i in range(0, self.process_count):
process = Process(target=self.func, args=[self.queue])
process.daemon = True
process.start()
95 changes: 95 additions & 0 deletions emails/testsuite/contrib/local-smtpd/secure_smtpd/proxy_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import socket
import smtplib
import secure_smtpd
from .smtp_server import SMTPServer
from .store_credentials import StoreCredentials

class ProxyServer(SMTPServer):
"""Implements an open relay. Inherits from secure_smtpd, so can handle
SSL incoming. Modifies attributes slightly:

* if "ssl" is true accepts SSL connections inbound and connects via SSL
outbound
* adds "ssl_out_only", which can be set to True when "ssl" is False so that
inbound connections are in plain text but outbound are in SSL
* adds "debug", which if True copies all inbound messages to logger.info()
* ignores any credential validators, passing any credentials upstream
"""
def __init__(self, *args, **kwargs):
self.ssl_out_only = False
if 'ssl_out_only' in kwargs:
self.ssl_out_only = kwargs.pop('ssl_out_only')

self.debug = False
if 'debug' in kwargs:
self.debug = kwargs.pop('debug')

kwargs['credential_validator'] = StoreCredentials()
SMTPServer.__init__(self, *args, **kwargs)

def process_message(self, peer, mailfrom, rcpttos, data):
if self.debug:
# ------------------------
# stolen directly from stmpd.DebuggingServer
inheaders = 1
lines = data.split('\n')
self.logger.info('---------- MESSAGE FOLLOWS ----------')
for line in lines:
# headers first
if inheaders and not line:
self.logger.info('X-Peer: %s', peer[0])
inheaders = 0
self.logger.info(line)
self.logger.info('------------ END MESSAGE ------------')

# ------------------------
# following code is direct from smtpd.PureProxy
lines = data.split('\n')
# Look for the last header
i = 0
for line in lines:
if not line:
break
i += 1
lines.insert(i, 'X-Peer: %s' % peer[0])
data = '\n'.join(lines)
self._deliver(mailfrom, rcpttos, data)

def _deliver(self, mailfrom, rcpttos, data):
# ------------------------
# following code is adapted from smtpd.PureProxy with modifications to
# handle upstream SSL
refused = {}
try:
if self.ssl or self.ssl_out_only:
s = smtplib.SMTP_SSL()
else:
s = smtplib.SMTP()

s.connect(self._remoteaddr[0], self._remoteaddr[1])
if self.credential_validator.stored:
# we had credentials passed in, use them
s.login(
self.credential_validator.username,
self.credential_validator.password
)
try:
refused = s.sendmail(mailfrom, rcpttos, data)
if refused != {}:
self.logger.error('some connections refused %s', refused)
finally:
s.quit()
except smtplib.SMTPRecipientsRefused as e:
self.logger.exception('')
refused = e.recipients
except (socket.error, smtplib.SMTPException) as e:
self.logger.exception('')

# All recipients were refused. If the exception had an associated
# error code, use it. Otherwise,fake it with a non-triggering
# exception code.
errcode = getattr(e, 'smtp_code', -1)
errmsg = getattr(e, 'smtp_error', 'ignore')
for r in rcpttos:
refused[r] = (errcode, errmsg)
return refused
Loading