18.6. 理解和编写重写规则

可以说,sendmail 最强大的功能是重写规则。sendmail 使用重写规则来确定如何处理收到的邮件消息。sendmail 通过称为规则集的重写规则集合来传递邮件消息标头中的地址。重写规则将邮件地址从一种形式转换为另一种形式,您可以将其视为编辑器中的命令,该命令将所有与指定模式匹配的文本替换为另一个文本。

每个规则都有一个左侧和一个右侧,它们之间至少用一个制表符分隔。当 sendmail 处理邮件时,它会扫描重写规则,查找左侧的匹配项。如果地址与重写规则的左侧匹配,则该地址将被右侧替换并再次处理。

18.6.1. sendmail.cf R 和 S 命令

sendmail.cf文件中,规则集是使用编码为以下形式的命令定义的Sn,其中n指定被认为是当前的规则集。

规则本身出现在编码为 R 的命令中。当读取每个 R 命令时,它都会被添加到当前规则集中。

如果您只处理sendmail.mc文件,您根本不需要担心 S 命令,因为宏会为您构建这些命令。您将需要手动编写您的 R 规则。

sendmail 规则集因此看起来像
Sn
Rlhs rhs
Rlhs2 rhs2

18.6.2. 一些有用的宏定义

sendmail 在内部使用许多标准宏定义。在编写规则集中,最有用的是这些

$j

此主机的完全限定域名。

$w

FQDN 的主机名组件。

$m

FQDN 的域名组件。

我们可以将这些宏定义合并到我们的重写规则中。我们的 Virtual Brewery 配置使用$m宏。

18.6.3. 左侧

在重写规则的左侧,您指定一个模式,该模式将匹配您希望转换的地址。大多数字符按字面意思匹配,但有许多字符具有特殊含义;这些字符在以下列表中描述。左侧的重写规则是

$@

完全匹配零个标记

$*

匹配零个或多个标记

$+

匹配一个或多个标记

$-

完全匹配一个标记

$=x

匹配类中的任何短语x

$~x

匹配不在类中的任何单词x

标记是由空格分隔的字符字符串。没有办法在标记中包含空格,也没有必要,因为表达式模式足够灵活,可以解决这种需求。当规则与地址匹配时,表达式中每个模式匹配的文本将被分配给特殊变量,我们将在右侧使用这些变量。唯一的例外是$@,它不匹配任何标记,因此永远不会生成要在右侧使用的文本。

18.6.4. 右侧

当重写规则的左侧与地址匹配时,原始文本将被删除,并替换为规则的右侧。右侧的所有标记都按字面意思复制,除非它们以美元符号开头。与左侧一样,右侧也可以使用许多元符号。这些在以下列表中描述。右侧的重写规则是

$n

此元符号将被替换为来自左侧的第n'个表达式。

$[name$]

此元符号将主机名解析为规范名称。它被提供的主机名的规范形式替换。

$(map key $@arguments $:default $)

这是查找的更通用形式。输出是在名为 map 的映射中查找key的结果,传递arguments作为参数。map 可以是 sendmail 支持的任何映射,例如virtusertable,我们稍后会对此进行描述。如果查找不成功,default将输出。如果未提供默认值且查找失败,则输入不变,并且key将输出。

$>n

这将导致解析此行的其余部分,然后将其提供给规则集n进行评估。调用的规则集的输出将作为此规则的输出写入。这是允许规则调用其他规则集的机制。

$#mailer

此元符号导致规则集评估停止,并指定邮件程序,该邮件程序应用于在邮件传递的下一步中传输此消息。此元符号应仅从规则集 0 或其子例程中调用。这是地址解析的最后阶段,应伴随接下来的两个元符号。

$@host

此元符号指定此消息将转发到的主机。如果目标主机是本地主机,则可以省略它。host可能是以冒号分隔的目标主机列表,这些主机将按顺序尝试传递消息。

$:user

此元符号指定邮件消息的目标用户。

通常,会重复尝试匹配的重写规则,直到它无法匹配为止,然后解析移至下一个规则。可以通过在右侧前面加上以下列表中描述的两个特殊右侧元符号之一来更改此行为。右侧循环控制元符号的重写规则是

$@

此元符号导致规则集返回,并将右侧的其余部分作为值。规则集中没有其他规则被评估。

$:

此元符号导致此规则立即终止,但当前规则集的其余部分将被评估。

18.6.5. 简单的规则模式示例

为了更好地了解宏替换模式如何操作,请考虑以下规则左侧
$* < $+ >

此规则匹配“零个或多个标记,后跟 < 字符,后跟一个或多个标记,后跟 > 字符。”

如果此规则应用于brewer@vbrew.comHead Brewer < >,则该规则将不匹配。第一个字符串将不匹配,因为它不包含 < 字符,而第二个字符串将失败,因为$+匹配一个或多个标记,并且在<>字符之间没有标记。在规则不匹配的任何情况下,都不会使用规则的右侧。

如果该规则应用于Head Brewer < brewer@vbrew.com >,则该规则将匹配,并且在右侧$1将被替换为Head Brewer$2将被替换为brewer@vbrew.com.

如果该规则应用于< brewer@vbrew.com >该规则将匹配,因为$*匹配零个或多个标记,并且在右侧$1将被替换为空字符串。

18.6.6. 规则集语义

每个 sendmail 规则集都被调用以执行邮件处理中的不同任务。当您编写规则时,了解每个规则集应该做什么非常重要。我们将查看 m4 配置脚本允许我们修改的每个规则集

LOCAL_RULE_3

规则集 3 负责将任意格式的地址转换为 sendmail 将随后处理的通用格式。预期的输出格式是熟悉的local-part@host-domain-spec.

规则集 3 应将转换后的地址的主机名部分放在<>字符内,以便于后续规则集进行解析。规则集 3 在 sendmail 对电子邮件地址进行任何其他处理之前应用,因此,如果您希望 sendmail 网关来自使用某些不寻常地址格式的系统的邮件,则应使用LOCAL_RULE_3宏来转换地址为通用格式。

LOCAL_RULE_0 和 LOCAL_NET_CONFIG

规则集 0 在规则集 3 之后由 sendmail 应用于收件人地址。LOCAL_NET_CONFIG宏导致规则插入到规则集 0 的下半部分

规则集 0 预计执行消息到收件人的传递,因此它必须解析为三元组,该三元组指定邮件程序、主机和用户。这些规则将放置在您可能包含的任何智能主机定义之前,因此,如果您添加适当解析地址的规则,则与规则匹配的任何地址将不会由智能主机处理。这就是我们在示例中处理本地 LAN 上用户的直接 smtp 的方式。

LOCAL_RULE_1 和 LOCAL_RULE_2

规则集 1 应用于所有发件人地址,规则集 2 应用于所有收件人地址。它们通常都是空的。

18.6.6.1. 解释我们示例中的规则

我们在 示例 18-3 中的示例使用了LOCAL_NET_CONFIG宏来声明本地规则,该规则确保我们域内的任何邮件都使用 smtp 邮件程序直接传递。现在我们已经了解了重写规则是如何构建的,我们将能够理解此规则的工作原理。让我们再看一下它。

示例 18-3。来自 vstout.uucpsmtp.m4 的重写规则

LOCAL_NET_CONFIG
# This rule ensures that all local mail is delivered using the
# smtp transport, everything else will go via the smart host.
R$* < @ $* .$m. > $*  $#smtp $@ $2.$m. $: $1 < @ $2.$m. > $3

我们知道LOCAL_NET_CONFIG宏将导致规则插入到规则集 0 的末尾附近,但在任何智能主机定义之前。我们还知道规则集 0 是要执行的最后一个规则集,它应该解析为指定邮件程序、用户和主机的三元组。

我们可以忽略这两个注释行;它们没有任何有用的作用。规则本身是以R开头的行。我们知道Rsendmail 命令,它将此规则添加到当前规则集中,在本例中是规则集0。让我们依次查看左侧和右侧。

左侧看起来像$* < @ $* .$m. > $*.

规则集 0 期望 < 和 > 字符,因为它由规则集 3 提供。规则集 3 将地址转换为通用形式,并且为了更易于解析,它还将邮件地址的主机部分放在 <> 中。

此规则匹配看起来像这样的任何邮件地址'DestUser < @ somehost.ourdomain. > Some Text'。也就是说,它匹配我们域内任何主机上任何用户的邮件。

您会记得,重写规则左侧的元符号匹配的文本被分配给宏定义,以便在右侧使用。在我们的示例中,第一个$*匹配从地址开头到 < 字符的所有文本。所有这些文本都分配给$1以便在右侧使用。类似地,第二个$*在我们的重写规则中分配给$2,最后一个分配给$3.

我们现在有足够的知识来理解左侧。此规则匹配我们域内任何主机上任何用户的邮件。它将用户名分配给 $1,将主机名分配给$2,并将任何尾随文本分配给$3。然后调用右侧来处理这些。

现在让我们看看我们期望看到的输出。我们的示例重写规则的右侧看起来像$#smtp $@ $2.$m. $: $1 < @ $2.$m. > $3.

当处理规则集的右侧时,将解释每个元符号并进行相关替换。

这个$#元符号导致此规则解析为特定的邮件程序,在我们的示例中是 smtp

这个$@解析目标主机。在我们的示例中,目标主机指定为$2.$m.,这是我们域中主机上的完全限定域名。FQDN 由分配给$2的主机名组件与我们的域名 (.$m.) 附加而成。

这个$:元符号指定目标用户,我们再次从左侧捕获了目标用户,并将其存储在$1.

我们使用从规则左侧收集的数据来保留 <> 部分的内容和任何尾随文本。

由于此规则解析为邮件程序,因此消息将转发到邮件程序以进行传递。在我们的示例中,消息将使用 SMTP 协议转发到目标主机。