【问题标题】:Python 3 SMTP aiosmtpd proxy/relayPython 3 SMTP aiosmtpd 代理/中继
【发布时间】:2017-04-07 13:07:34
【问题描述】:

我正在尝试使用替换 smtpd 的新 aiosmtpd 库创建开放式 SMTP 中继。下面的程序实例化了一个Proxy 处理程序,该处理程序被传递到邮件控制器,然后在后台启动。然后使用连接到中继的标准smtplib 客户端创建一条消息。

一切都很好,直到客户端和中继之间的 SMTP 对话以消息永不离开中继结束。中继从不回复250 OK\r\nctrl+c 表示sendmail 正在等待回复。

有什么想法吗?脚本是否遗漏了什么?

编辑mail.example.com 只是一个示例服务器。 smtpd DebuggingServer 在使用 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...

脚本:

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)

这是脚本的输出:

 $ 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

为了比较,这里是使用调试处理程序的输出:

 $ 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

【问题讨论】:

  • 我这里没有 iosmtpd,但一切都像 aiosmtpd 在收到消息后未能发送250 Ok\r\n 一样。我假设当您启动它作为 mail.example.com:20 的中继时,它可能会尝试联系中继主机并且可能会失败。你到底想做什么?
  • @SergeBallesta 没有注意到缺少的250 OK\r\n。目标是创建一个开放中继,将 SMTP 客户端发送的消息转发到提供的目标 SMTP 服务器。除了示例 mail.example.com 之外,电子邮件服务器也会发生相同的行为。
  • 如果发生连接错误,代理是否应该引发异常和/或至少让传递函数将控制权返回给主应用程序?
  • 您是否尝试中继到您知道它将接受邮件的服务器,例如 smtpd DebuggingServer?
  • @SergeBallesta 刚刚试过。行为没有改变。用这个更新了问题。

标签: python smtplib aiosmtpd


【解决方案1】:

编辑:错误在 aiosmtpd 1.0b1 中是 fixed,因此升级应该可以解决问题。

在 aiosmtpd 1.0a4 中,Proxy.handle_DATA 中的未捕获异常(使用 data 作为 str 而不是字节)导致 asyncio 任务停止,但该异常永远不会传播。

如果您升级到 1.0a5,您将正确打印异常:“错误:(TypeError) 无法在类似字节的对象上使用字符串模式”。问题是,从 1.0a5 开始,aiosmtpd 中的 Proxy 设计用于在 aiosmtpd.smtp.SMTP 对象上设置 decode_data=True 的 Controller 子类,但默认情况下,decode_data=False 会导致错误。 (在我看来,Proxy 应该改为与decode_data=False 一起使用,所以我打开了PR #74。)

因此,要在 aiosmtpd 1.0a4 或 1.0a5 中使用代理,您需要从 aiosmtpd/tests/test_handlers.py 复制并使用 UTF8Controller 子类。我已经用 1.0a4 和 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)

【讨论】:

  • 这个解决方案确实解决了这个问题。希望 GitHub 上的拉取请求能够通过。
  • 拉取请求已为 1.0b1 合并,因此您的原始代码现在无需修改即可使用 aiosmtpd。
猜你喜欢
  • 2017-10-17
  • 1970-01-01
  • 2021-02-25
  • 1970-01-01
  • 2016-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-11
相关资源
最近更新 更多