I am trying to make an open SMTP relay using the new aiosmtpd
library that replaces smtpd
. The program below instantiates a Proxy
handler that is passed onto the mail controller, which is started afterwards in the background. A message is then created with a standard smtplib
client that connects to the relay.
All is good until the SMTP conversation between the client and the relay ends with the message never leaving the relay. The relay never replies with a 250 OK\r\n
and a ctrl+c
shows that sendmail
is waiting for a reply.
Any ideas? Is the script missing something?
Edit: mail.example.com
is only an example server. An smtpd DebuggingServer
prints nothing upon execution of the script with relay = aiosmtpd.handlers.Proxy("localhost", 1025)
.
$ python3.6 -m smtpd -n -c DebuggingServer -d localhost:1025
DebuggingServer started at Fri Apr 7 18:41:09 2017
Local addr: ('localhost', 1025)
Remote addr:('localhost', 25)
...nothing printed out...
Script:
from aiosmtpd.handlers import Debugging, Proxy
from aiosmtpd.controller import Controller
from smtplib import SMTP
# relay = aiosmtpd.handlers.Debugging()
relay = aiosmtpd.handlers.Proxy("localhost", 1025)
# relay = aiosmtpd.handlers.Proxy("mail.example.com", 25)
controller = Controller(relay)
controller.start()
print(controller, controller.hostname, controller.port)
input("ready... press enter to continue")
print("creating SMTP with debug")
client = SMTP()
client.set_debuglevel(1)
print("connecting to the SMTP server")
client.connect(controller.hostname, controller.port)
print("sending message")
client.sendmail('alice@example.com',
['bob@example.com'], """\
From: Alice <alice@example.com>
To: Bob <bob@example.com>
Subject: Title
Body.
""")
print("stopping controller")
controller.stop()
print("checking if controller really stopped")
client.connect(controller.hostname, controller.port)
Here is the output of the script:
$ python3.6 relay.py
<aiosmtpd.controller.Controller object at 0x10199f710> ::0 8025
ready... press enter to continue
creating SMTP with debug
connecting to the SMTP server
connect: ('::0', 8025)
connect: to ('::0', 8025) None
reply: b'220 localhost Python SMTP 1.0a4\r\n'
reply: retcode (220); Msg: b'localhost Python SMTP 1.0a4'
connect: b'localhost Python SMTP 1.0a4'
sending message
send: 'ehlo localhost\r\n'
reply: b'250-localhost\r\n'
reply: b'250-SIZE 33554432\r\n'
reply: b'250-8BITMIME\r\n'
reply: b'250 HELP\r\n'
reply: retcode (250); Msg: b'localhost\nSIZE 33554432\n8BITMIME\nHELP'
send: 'mail FROM:<alice@example.com> size=85\r\n'
reply: b'250 OK\r\n'
reply: retcode (250); Msg: b'OK'
send: 'rcpt TO:<bob@example.com>\r\n'
reply: b'250 OK\r\n'
reply: retcode (250); Msg: b'OK'
send: 'data\r\n'
reply: b'354 End data with <CR><LF>.<CR><LF>\r\n'
reply: retcode (354); Msg: b'End data with <CR><LF>.<CR><LF>'
data: (354, b'End data with <CR><LF>.<CR><LF>')
send: b'From: Alice <alice@example.com>\r\nTo: Bob <bob@example.com>\r\nSubject: Title\r\n\r\nBody.\r\n.\r\n'
...nothing happens at this point...
^CTraceback (most recent call last):
File "relay.py", line 49, in <module>
""")
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/smtplib.py", line 881, in sendmail
(code, resp) = self.data(msg)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/smtplib.py", line 568, in data
(code, msg) = self.getreply()
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/smtplib.py", line 386, in getreply
line = self.file.readline(_MAXLINE + 1)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/socket.py", line 586, in readinto
return self._sock.recv_into(b)
KeyboardInterrupt
For comparison, here is the output using the debug handler:
$ python3.6 relay.py
<aiosmtpd.controller.Controller object at 0x10189f710> ::0 8025
ready... press enter to continue
creating SMTP with debug
connecting to the SMTP server
connect: ('::0', 8025)
connect: to ('::0', 8025) None
reply: b'220 localhost Python SMTP 1.0a4\r\n'
reply: retcode (220); Msg: b'localhost Python SMTP 1.0a4'
connect: b'localhost Python SMTP 1.0a4'
sending message
send: 'ehlo localhost\r\n'
reply: b'250-localhost\r\n'
reply: b'250-SIZE 33554432\r\n'
reply: b'250-8BITMIME\r\n'
reply: b'250 HELP\r\n'
reply: retcode (250); Msg: b'localhost\nSIZE 33554432\n8BITMIME\nHELP'
send: 'mail FROM:<alice@example.com> size=85\r\n'
reply: b'250 OK\r\n'
reply: retcode (250); Msg: b'OK'
send: 'rcpt TO:<bob@example.com>\r\n'
reply: b'250 OK\r\n'
reply: retcode (250); Msg: b'OK'
send: 'data\r\n'
reply: b'354 End data with <CR><LF>.<CR><LF>\r\n'
reply: retcode (354); Msg: b'End data with <CR><LF>.<CR><LF>'
data: (354, b'End data with <CR><LF>.<CR><LF>')
send: b'From: Alice <alice@example.com>\r\nTo: Bob <bob@example.com>\r\nSubject: Title\r\n\r\nBody.\r\n.\r\n'
---------- MESSAGE FOLLOWS ----------
mail options: ['SIZE=85']
rcpt options: []
From: Alice <alice@example.com>
To: Bob <bob@example.com>
Subject: Title
X-Peer: ('::1', 64397, 0, 0)
Body.
------------ END MESSAGE ------------
reply: b'250 OK\r\n'
reply: retcode (250); Msg: b'OK'
data: (250, b'OK')
stopping controller
checking if controller really stopped
connect: ('::0', 8025)
connect: to ('::0', 8025) None
Traceback (most recent call last):
File "relay.py", line 51, in <module>
client.connect(controller.hostname, controller.port)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/smtplib.py", line 335, in connect
self.sock = self._get_socket(host, port, self.timeout)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/smtplib.py", line 306, in _get_socket
self.source_address)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/socket.py", line 722, in create_connection
raise err
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/socket.py", line 713, in create_connection
sock.connect(sa)
ConnectionRefusedError: [Errno 61] Connection refused
EDIT: The bug has been fixed in aiosmtpd 1.0b1, so an upgrade should resolve the issue.
In aiosmtpd 1.0a4, an uncaught exception in Proxy.handle_DATA
(using data
as a str instead of bytes) causes the asyncio task to stop, but the exception is never propagated.
If you upgrade to 1.0a5 you'll get the exception printed properly: "Error: (TypeError) cannot use a string pattern on a bytes-like object". The problem is that as of 1.0a5, Proxy
in aiosmtpd is designed for use in a Controller subclass that sets decode_data=True
on the aiosmtpd.smtp.SMTP
object, but by default, decode_data=False
which causes the error. (In my opinion Proxy
should be changed to work with decode_data=False
, so I've opened PR #74.)
Thus, to use Proxy in aiosmtpd 1.0a4 or 1.0a5 you need to copy and use the UTF8Controller
subclass from aiosmtpd/tests/test_handlers.py
. I've tested the following script with both 1.0a4 and 1.0a5:
from aiosmtpd.handlers import Debugging, Proxy
from aiosmtpd.controller import Controller
from aiosmtpd.smtp import SMTP as SMTPServer
from smtplib import SMTP as SMTPClient
class UTF8Controller(Controller):
def factory(self):
return SMTPServer(self.handler, decode_data=True)
# relay = Debugging()
relay = Proxy("localhost", 1025)
# relay = Proxy("mail.example.com", 25)
controller = UTF8Controller(relay)
controller.start()
print(controller, controller.hostname, controller.port)
input("ready... press enter to continue")
print("creating SMTP with debug")
client = SMTPClient()
client.set_debuglevel(1)
print("connecting to the SMTP server")
client.connect(controller.hostname, controller.port)
print("sending message")
client.sendmail('alice@example.com',
['bob@example.com'], """\
From: Alice <alice@example.com>
To: Bob <bob@example.com>
Subject: Title
Body.
""")
print("stopping controller")
controller.stop()
print("checking if controller really stopped")
client.connect(controller.hostname, controller.port)