# `smtpd` --- SMTP 服务器

源代码: Lib/smtpd.py

该模块提供了几个类来实现 SMTP （电子邮件）服务器。

3.6 版后已移除: `smtpd` 将在 Python 3.12 中移除（请参阅 PEP 594 了解详情）。 aiosmtpd 包是这个模块的推荐替代品。 它基于 `asyncio` 并提供了更简洁直观的 API。

有几个服务器的实现；一个是通用的无为实现，可以被重写，而另外两个则提供特定的邮件发送策略。

此外， SMTPChannel 可以被扩展以实现与 SMTP 客户端非常具体的交互行为。

该代码支持 RFC 5321 ，加上 RFC 1870 SIZE和 RFC 6531 SMTPUTF8 扩展。

## SMTPServer 对象

class `smtpd.``SMTPServer`(localaddr, remoteaddr, data_size_limit=33554432, map=None, enable_SMTPUTF8=False, decode_data=False)

  新建一个 `SMTPServer` 对象，它会绑定到本机地址 localaddr。 它将把 remoteaddr 当作上游 SMTP 中继器。 localaddr 和 remoteaddr 都应当是 (host, port) 元组。 该对象继承自 `asyncore.dispatcher`，因而会在实例化时将自己插入到 `asyncore` 的事件循环。

  data_size_limit 指定将在 `DATA` 命令中被接受的最大字节数。 值为 `None` 或 `0` 表示无限制。

  map 是用于连接的套接字映射（初始为空的字典是适当的值）。 如果未指定则会使用 `asyncore` 全局套接字映射。

  enable_SMTPUTF8 决定是否应当启用 `SMTPUTF8` 扩展（如 RFC 6531 所定义的。 默认值为 `False`。 当设为 `True` 时，会接受 `SMTPUTF8` 作为 `MAIL` 命令的形参并在被提供时将其传给 `kwargs['mail_options']` 列表中的 `process_message()`。 decode_data 和 enable_SMTPUTF8 不可同时被设为 `True`。

  decode_data 指明 SMTP 事务的数据部分是否应当使用 UTF-8 来解码。 当 decode_data 为 `False` 时（默认值），服务器会声明 `8BITMIME` 扩展 ( RFC 6152)，接受来自 `MAIL` 命令的 `BODY=8BITMIME` 形参，并在该形参存在时将其传给 `kwargs['mail_options']` 列表中的 `process_message()` 方法。 decode_data 和 enable_SMTPUTF8 不可同时被设为 `True`。

  `process_message`(peer, mailfrom, rcpttos, data, **kwargs)

    引发 `NotImplementedError` 异常。 请在子类中重载此方法以实际运用此消息。 在构造器中作为 remoteaddr 传入的任何东西都可以 `_remoteaddr` 属性的形式来访问。 peer 是远程主机的地址，mailfrom 是封包的发送方，rcpttos 是封包的接收方而 data 是包含电子邮件内容的字符串（应该为 RFC 5321 格式）。

    如果构造器关键字参数 decode_data 被设为 `True`，则 data 参数将为 Unicode 字符串。 如果被设为 `False`，则将为字节串对象。

    kwargs 是包含附加信息的字典。 如果给出 `decode_data=True` 作为初始参数则该字典为空，否则它会包含以下的键:

    > mail_options:

    >   由 `MAIL` 命令所接收的所有参数组成的列表 (其元素为大写形式的字符串；例如: `['BODY=8BITMIME', 'SMTPUTF8']`)。

    > rcpt_options:

    >   与 mail_options 类似但是针对 `RCPT` 命令。 目前不支持任何 `RCPT TO` 选项，因此其值将总是为空列表。

    `process_message` 的实现应当使用 `**kwargs` 签名来接收任意关键字参数，因为未来的增强特性可能会向 kwargs 字典添加新键。

    返回 `None` 以请求一个正常的 `250 Ok` 响应；在其他情况下则以 RFC 5321 格式返回所需的响应字符串。

  `channel_class`

    重载这个子类以使用自定义的 `SMTPChannel` 来管理 SMTP 客户端。

  3.4 新版功能: map 构造器参数。

  在 3.5 版更改: localaddr 和 remoteaddr 现在可以包含 IPv6 地址。

  3.5 新版功能: decode_data 和 enable_SMTPUTF8 构造器形参，以及当 decode_data 为 `False` 时传给 `process_message()` 的 kwargs 形参。

  在 3.6 版更改: decode_data 现在默认为 `False`。

## DebuggingServer 对象

class `smtpd.``DebuggingServer`(localaddr, remoteaddr)

  创建一个新的调试服务器。 参数是针对每个 `SMTPServer`。 消息将被丢弃，并在 stdout 上打印出来。

## PureProxy 对象

class `smtpd.``PureProxy`(localaddr, remoteaddr)

  创建一个新的纯代理服务器。 参数是针对每个 `SMTPServer`。 一切都将被转发到 remoteaddr。 请注意运行此对象有很大的机会令你成为一个开放的中继站，所以需要小心。

## MailmanProxy 对象

class `smtpd.``MailmanProxy`(localaddr, remoteaddr)

  Deprecated since version 3.9, will be removed in version 3.11: `MailmanProxy` 已被弃用，它依赖于一个已不存在的 `Mailman` 模块因而本来就不再可用了。

  创建一个新的纯代理服务器。 参数是针对每个 `SMTPServer`。 一切都将被转发到 remoteaddr，除非本地 mailman 配置知道另一个地址，在那种情况下它将由 mailman 来处理。 请注意运行此对象有很大的机会令你成为一个开放的中继站，所以需要小心。

## SMTPChannel 对象

class `smtpd.``SMTPChannel`(server, conn, addr, data_size_limit=33554432, map=None, enable_SMTPUTF8=False, decode_data=False)

  创建一个新的 `SMTPChannel` 对象，该对象会管理服务器和单个 SMTP 客户端之间的通信。

  conn 和 addr 是针对下述的每个实例变量。

  data_size_limit 指定将在 `DATA` 命令中被接受的最大字节数。 值为 `None` 或 `0` 表示无限制。

  enable_SMTPUTF8 确定 `SMTPUTF8` 扩展 (如 RFC 6531 所定义的) 是否应当被启用。 默认值为 `False`。 decode_data 和 enable_SMTPUTF8 不能被同时设为 `True`。

  可以在 map 中指定一个字典以避免使用全局套接字映射。

  decode_data 指明 SMTP 事务的数据部分是否应当使用 UTF-8 来解码。 默认值为 `False`。 decode_data 和 enable_SMTPUTF8 不能被同时设为 `True`。

  要使用自定义的 SMTPChannel 实现你必须重载你的 `SMTPServer` 的 `SMTPServer.channel_class`。

  在 3.5 版更改: 添加了 decode_data 和 enable_SMTPUTF8 形参。

  在 3.6 版更改: decode_data 现在默认为 `False`。

  `SMTPChannel` 具有下列实例变量:

  `smtp_server`

    存放生成此通道的 `SMTPServer`。

  `conn`

    存放连接到客户端的套接字对象。

  `addr`

    存放客户端的地址，`socket.accept` 所返回的第二个值。

  `received_lines`

    存放从客户端接收的行字符串列表 (使用 UTF-8 解码)。 所有行的 `"\r\n"` 行结束符都会被转写为 `"\n"`。

  `smtp_state`

    存放通道的当前状态。 其初始值将为 `COMMAND` 而在客户端发送 "DATA" 行后将为 `DATA`。

  `seen_greeting`

    存放包含客户端在其 "HELO" 中发送的问候信息的字符串。

  `mailfrom`

    存放包含客户端在 "MAIL FROM:" 行中标识的地址的字符串。

  `rcpttos`

    存放包含客户端在 "RCPT TO:" 行中标识的地址的字符串。

  `received_data`

    存放客户端在 DATA 状态期间发送的所有数据的字符串，直至但不包括末尾的 `"\r\n.\r\n"`。

  `fqdn`

    存放由 `socket.getfqdn()` 所返回的服务器完整限定域名。

  `peer`

    存放由 `conn.getpeername()` 所返回的客户端对等方名称，其中 `conn` 为 `conn`。

  `SMTPChannel` 在接收到来自客户端的命令行时会通过发起调用名为 `smtp_<command>` 的方法来进行操作。 在基类 `SMTPChannel` 中具有用于处理下列命令（并对他们作出适当反应）的方法:

  命令 | 所采取的行动
  --- | ---
  HELO | 接受来自客户端的问候语，并将其存储在 `seen_greeting` 中。将服务器设置为基本命令模式。
  EHLO | 接受来自客户的问候并将其存储在 `seen_greeting` 中。将服务器设置为扩展命令模式。
  NOOP | 不采取任何行动。
  QUIT | 干净地关闭连接。
  MAIL | 接受 "MAIL FROM:" 句法并将所提供的地址保存为 `mailfrom`。 在扩展命令模式下，还接受 RFC 1870 SIZE 属性并根据 data_size_limit 的值作出适当返应。
  RCPT | 接受 "RCPT TO:" 句法并将所提供的地址保存在 `rcpttos` 列表中。
  RSET | 重置 `mailfrom`, `rcpttos`, 和 `received_data` ，但不重置问候语。
  DATA | 将内部状态设为 `DATA` 并将来自客户端的剩余行保存在 `received_data` 中直至接收到终止符 `"\r\n.\r\n"`。
  HELP | 返回有关命令语法的最少信息
  VRFY | 返回代码252（服务器不知道该地址是否有效）
  EXPN | 报告该命令未实现。