Skip to main content

4. The check_host() Function (check_host() 函数)

4. The check_host() Function (check_host() 函数)

此描述不是应用程序编程接口定义, 而是用于说明算法的函数描述。符合标准的 SPF 实现必须产生与此描述在语义上等效的结果。

check_host() 函数获取 SPF 记录, 解析它们, 并评估它们以确定特定主机是否被允许或不被允许使用给定标识发送邮件。执行此检查的接收 ADMD 必须按照此处所述正确评估 check_host() 函数。

实现可以使用与此处定义的规范算法不同的算法, 只要在所有情况下结果相同。

4.1 Arguments (参数)

check_host() 函数采用以下参数:

<ip> - 发出邮件的 SMTP 客户端的 IP 地址, 可以是 IPv4 或 IPv6。

<domain> - 提供所寻求的授权信息的域; 最初是 "MAIL FROM" 或 "HELO" 标识的域部分。

<sender> - "MAIL FROM" 或 "HELO" 标识。

对于递归评估, <sender> 的域部分可能与 <domain> 参数在 check_host() 最初评估时不同。在大多数其他情况下, 它将是相同的(参见下面的第 5.2 节)。第 4.6.4 节中描述的 SPF 术语的总体 DNS 查询限制必须作为单个全局限制进行跟踪, 适用于所有评估, 而不仅仅是单个递归评估实例。

请注意, <domain> 参数可能不是格式良好的域名。例如, 如果反向路径为空, 则使用 EHLO/HELO 域及其相关问题(参见第 2.3 节)。在这些情况下, check_host() 在第 4.3 节中被定义为返回 "none" 结果。

4.2 Results (结果)

check_host() 函数可以返回第 2.6 节中描述的几个结果之一。根据结果, 要采取的操作由接收方的本地策略确定。这在第 8 节中讨论。

4.3 Initial Processing (初始处理)

如果 <domain> 格式错误(例如, 标签长度超过 63 个字符, 非结尾的零长度标签等)或不是多标签域名, 或者如果 DNS 查询返回 "Name Error"(RCODE 3, 也称为 "NXDOMAIN" [RFC2308]), 则 check_host() 立即返回结果 "none"。DNS RCODE 在 [RFC1035] 中定义。格式正确的域是 [RFC1983] 中定义的完全限定域。也就是说, 在 DNS 中, 它们相对于根被隐式限定(参见 [RFC1034] 的第 3.1 节)。国际化域名必须编码为 A-label, 如 [RFC5890] 的第 2.3 节所述。

如果 <sender> 没有 local-part, 则用字符串 "postmaster" 替换 local-part。

4.4 Record Lookup (记录查询)

根据记录的发布方式(参见上面的第 3 节), 需要对 <domain> 名称进行 DNS 查询, 仅查询类型 TXT。

如果 DNS 查询返回服务器失败(RCODE 2)或其他错误(RCODE 不是 0 或 3), 或者如果查询超时, 则 check_host() 立即终止, 结果为 "temperror"。

4.5 Selecting Records (选择记录)

记录以版本部分开头:

record = version terms *SP
version = "v=spf1"

从查询返回的记录集开始, 丢弃不以恰好 "v=spf1" 的版本部分开头的记录。请注意, 版本部分由 SP 字符或记录结尾终止。例如, 版本部分为 "v=spf10" 的记录不匹配并被丢弃。

如果结果记录集不包含记录, 则 check_host() 产生 "none" 结果。如果结果记录集包含多个记录, 则 check_host() 产生 "permerror" 结果。

4.6 Record Evaluation (记录评估)

check_host() 函数解析和解释 SPF 记录以找到当前测试的结果。首先验证记录的语法, 如果记录中的任何地方存在任何语法错误, 则 check_host() 立即返回结果 "permerror", 不进行进一步的解释或评估。

4.6.1 Term Evaluation (术语评估)

有两种类型的术语: 机制(在第 5 节中定义)和修饰符(在第 6 节中定义)。记录包含这些的有序列表, 如以下增强巴科斯-诺尔范式(ABNF)中所指定。

terms = *( 1*SP ( directive / modifier ) )

directive = [ qualifier ] mechanism
qualifier = "+" / "-" / "?" / "~"
mechanism = ( all / include
/ a / mx / ptr / ip4 / ip6 / exists )
modifier = redirect / explanation / unknown-modifier
unknown-modifier = name "=" macro-string
; where name is not any known modifier

name = ALPHA *( ALPHA / DIGIT / "-" / "_" / "." )

大多数机制在名称后允许使用 ":" 或 "/" 字符。

修饰符始终在名称之后立即包含等号('=')字符, 并且在可能是 macro-string 一部分的任何 ":" 或 "/" 字符之前。

不包含 "=", ":", 或 "/" 中任何一个的术语是机制, 如第 5 节中定义。

根据 [RFC5234] 中 ABNF 表示法的定义, 机制和修饰符名称是不区分大小写的。

4.6.2 Mechanisms (机制)

从左到右依次考虑每个机制。如果没有更多的机制, 则结果是第 4.7 节中描述的默认结果。

当评估机制时, 可能发生三种情况之一: 它可以匹配, 不匹配, 或返回异常。

如果它匹配, 则处理结束, 限定符值作为该记录的结果返回。如果它不匹配, 则继续处理下一个机制。如果它返回异常, 则机制处理结束并返回异常值。

可能的限定符及其导致 check_host() 返回的结果如下:

"+" pass
"-" fail
"~" softfail
"?" neutral

限定符是可选的, 默认为 "+"。

当机制匹配且限定符为 "-" 时, 则返回 "fail" 结果并计算解释字符串, 如第 6.2 节所述。

第 5 节描述了具体的机制。

4.6.3 Modifiers (修饰符)

修饰符不是机制。它们不返回匹配或不匹配。相反, 它们提供额外的信息。虽然修饰符不直接影响记录的评估, 但 "redirect" 修饰符在评估所有机制之后具有影响。

4.6.4 DNS Lookup Limits (DNS 查询限制)

某些机制和修饰符(统称为 "术语")在评估时会导致 DNS 查询, 而有些则不会。以下术语会导致 DNS 查询: "include", "a", "mx", "ptr", 和 "exists" 机制, 以及 "redirect" 修饰符。SPF 实现必须在 SPF 评估期间将这些术语的总数限制为 10, 以避免对 DNS 造成不合理的负载。如果超过此限制, 则实现必须返回 "permerror"。其他术语 -- "all", "ip4", 和 "ip6" 机制, 以及 "exp" 修饰符 -- 在 SPF 评估时不会导致 DNS 查询("exp" 修饰符仅在稍后的时间导致查询), 它们的使用不受此限制。

在评估 "mx" 机制时, 查询的 "MX" 资源记录数量包含在上述 10 个导致 DNS 查询的机制/修饰符的总体限制中。除了该限制之外, 每个 "MX" 记录的评估绝对不能导致查询超过 10 个地址记录 -- "A" 或 "AAAA" 资源记录。如果超过此限制, 则 "mx" 机制必须产生 "permerror" 结果。

在评估 "ptr" 机制或 %{p} 宏时, 查询的 "PTR" 资源记录数量包含在上述 10 个导致 DNS 查询的机制/修饰符的总体限制中。除了该限制之外, 每个 "PTR" 记录的评估绝对不能导致查询超过 10 个地址记录 -- "A" 或 "AAAA" 资源记录。如果超过此限制, 则必须忽略除前 10 个之外的所有记录。

差异的原因是 MX 记录的集合和内容在发布 ADMD 的控制之下, 而 PTR 记录的集合和内容在实际建立连接的 IP 地址的所有者的控制之下。

这些限制是每个记录或宏中的每个机制, 并且是对上面指定的查询限制的补充。

MTA 或其他处理器应该对评估 check_host() 的最大经过时间施加限制。这样的限制应该至少允许 20 秒。如果超过这样的限制, 则授权的结果应该是 "temperror"。

如第 11.1 节末尾所述, 在某些情况下, 限制 DNS 查询返回正答案(RCODE 0)但应答计数为 0, 或 "Name Error"(RCODE 3)应答的 "术语" 数量可能很有用。这些有时统称为 "void lookups"。SPF 实现应该将 "void lookups" 限制为两个。实现可以选择使此类限制可配置。在这种情况下, 建议默认值为两个。超过限制会产生 "permerror" 结果。

4.7 Default Result (默认结果)

如果没有任何机制匹配并且没有 "redirect" 修饰符, 则 check_host() 返回结果 "neutral", 就像 "?all" 被指定为最后一个指令一样。如果存在 "redirect" 修饰符, 则 check_host() 按照第 6.1 节中的定义继续进行。

最好使用 "redirect" 修饰符或 "all" 机制来明确终止处理。虽然每个未明确终止的记录末尾都有一个隐式 "?all", 但当它被明确提供时, 有助于调试工作。

例如:

v=spf1 +mx -all

v=spf1 +mx redirect=_spf.example.com

4.8 Domain Specification (域规范)

这些机制和修饰符中的几个具有 <domain-spec> 部分。字符串受宏扩展(参见第 7 节)。生成的字符串是完全限定 DNS 名称的常见表示形式: 由句点分隔的一系列标签。此域在本文档的其余部分称为 <target-name>

注意: 宏扩展的结果不受任何进一步的转义。因此, 此工具无法生成 DNS 标签中合法的所有字符(例如, 控制字符)。但是, 此工具足够强大, 可以表达合法的主机名和在 DNS 中使用的常见实用程序标签(例如 "_spf")。

对于几个机制, <domain-spec> 是可选的。如果未提供, 则使用来自 check_host() 参数(参见第 4.1 节)的 <domain> 作为 <target-name>。"domain" 和 <target-name> 在宏扩展后在语法上是相同的。"domain" 是 check_host() 的输入值, 而 <target-name> 是由 check_host() 计算的。

使用语法上无效的域评估 check_host() 的结果是未定义的。

注意: 本文档及其前身没有规定正确处理语法上无效的 <domain-spec>(可能是宏扩展的结果)的规定, 根据 [RFC1035]。示例包括具有空标签的名称, 例如 "foo..example.com", 以及长度超过 63 个字符的标签。一些实现选择将此类错误视为不匹配, 因此忽略此类名称, 而其他实现返回 "permerror" 异常。