18.16 Postfix邮件系统
Postfix是另一种sendmail的替代软件。Wietse Venema到IBM的T. J. Watson研究中心(T. J. Watson Research Center)做学术休假的一年里,启动了Postfix这个项目。Postfix的设计目标包括:开放源代码的软件发布政策、快速性能、健壮、灵活和安全。它直接同Dan Bernstein编写的qmail展开了竞争。所有主要的Linux发行版本都包含Postfix,Mac OS X从10.3版开始用它取代sendmail作为其默认的邮件系统。
对于Postfix来说,最重要的事情有:首先,它马上就能使用(最简单的配置文件只有一两行);其次,它利用正则表达式映射高效地过滤电子邮件,特别是配合PCRE(Perl Compatible Regular Expression,兼容Perl的正则表达式)库使用效果颇佳。Postfix和sendmail兼容,因为Postfix的aliases和.forward文件与sendmail的对应文件在格式和语义上都相同。
Postfix使用ESMTP协议,它也支持虚拟域和垃圾邮件过滤功能。Postfix没有像sendmail那样使用一种重写地址的语言,相反,它依靠从纯文件、Berkeley DB、dbm、LDAP、NIS、NetInfo或者MySQL这类的数据库进行的表查询。
18.16.1 Postfix体系结构
Postfix由几个小的协作程序所组成,这些程序负责发送网络消息、接收消息、在本地投递邮件等。它们之间通过UNIX域套接口或者FIFO进行通信。这种体系结构与sendmail的体系结构非常不同,sendmail就在一个大程序里完成大部分工作。
master程序启动和监视所有的Postfix进程。它的配置文件master.cf列出了这些辅助程序,还有应该怎样启动它们的信息。在那个文件中设定的默认值适合于除了非常慢或者非常快的机器(或者网络)之外的所有情况使用。一般而言,不需要对它进行调整。常做的一项修改是注释掉一个程序,例如smtpd,在客户机不应该监听SMTP端口的时候这么做。
在投递电子邮件的过程中,那些最重要的服务器程序如图18.5所示。

图18.5 Postfix的服务器程序
18.16.2 接收邮件
smtpd在SMTP端口上收邮件,它还要检查连上来的客户机是否得到授权,能够发送它们正在尝试投递的邮件。当通过/usr/lib/sendmail这个Postfix兼容sendmail的程序往本地发邮件的时候,会在/var/spool/postfix/maildrop目录下写一个文件。pickup程序定期扫描这个目录,处理它发现的任何新文件。
传入的所有电子邮件都要经过cleanup,它根据canonical和virtual映射,加上缺少的信头,重写邮件地址。在把电子邮件插入到incoming队列之前,cleanup要用trivial-rewrite把它略做重写,这个程序对地址做一些小的修正,例如,给不完整的地址追加一个邮件域名。
18.16.3 邮件队列管理器
等候投递的邮件由qmgr控制,这个队列管理器管理着5个队列:
incoming(传入)—刚到的邮件;
active(活动)—正在投递的邮件;
deferred(推迟)—以前投递失败的邮件;
hold(约束)—队列中的邮件被系统管理员阻止发送;
corrupt(错误)—不可读或者不可分析的邮件。
队列管理器一般采用一种简单的FIFO(先进先出)策略来选择要处理的下一个消息,但它也支持一种复杂的抢先式算法,该算法优先发送收件人少的消息,把有大量收件人的批量发送邮件放到后面。
为了不让接收邮件的主机忙不过来,特别是当它刚刚宕过机之后,Postfix使用了一种慢启动算法来控制它尝试投递邮件的速度有多快。推迟发送的消息获得一个二次重发时间戳,这个时间按指数退避,从而不让无法投递的消息浪费资源。不可达的目的地会被缓存下来,以此避免做不必要的投递努力。
18.16.4 发邮件
trivial-rewrite帮助qmgr确定应该把一则消息发到哪里。trivial-rewrite做出的路由决策可以被transport映射所覆盖。
smtpd程序负责通过SMTP协议把邮件投递到远程主机。lmtp使用RFC 2022里制定的LMTP协议(Local Mail Transfer Protocol,本地邮件传送协议)投递邮件。LMTP基于SMTP,但这个协议已经做了修改,不要求邮件服务器管理邮件队列。这个邮寄程序(mailer)对于把电子邮件投递到Cyrus IMAP软件这样的邮箱服务器特别有用。
local的工作是向本地投递电子邮件。它在别名表里解析地址,按照从收件人的.forward文件里找到的指示处理邮件。消息不是被转发给另一个地址,传给一个外部程序处理,就是保存在该用户的邮件目录里。
virtual这个程序把电子邮件投递到“虚拟邮箱”,也就是说,和本地的Linux账号没有关系的邮箱,但仍然代表有效的邮件目的地。最后,pipe实现了通过外部程序投递邮件。
18.16.5 安全
Postfix在几个层面上落实安全措施。大多数的Postfix服务器程序都能运行在一个chroot过的环境下。它们是没有父子关系的独立程序,而且它们中也没有一个是setuid程序。放邮箱的目录的组可写权限给了postdrop这个组,postdrop程序的setgid就是这个组。
有一点令人印象深刻,除了DoS(拒绝服务攻击)之外,任何版本的Postfix里尚未出现其他被利用的安全漏洞。
18.16.6 Postfix命令和文档
用户可以通过几个命令行工具与这个邮件系统交互:
sendmail、mailq、newaliases—都是和sendmail兼容的替代程序;
postfix—启动和停止该邮件系统(必须以root身份运行);
postalias—构造、修改和查询别名表;
postcat—打印队列文件的内容;
postconf—显示和编辑main.cf这个主配置文件;
postmap—构造、修改或者查询查找表;
postuper—管理邮件队列。
Postfix的软件发布带有一套手册页,该手册描述了所有这些程序及其选项。此外,在www.postfix.org的在线文档说明了配置和管理Postfix的各个方面。在Postfix的软件发布中也包含了同样的文档,所以您应该能发现它们已经装到了您的系统上,通常是在一个叫做README_FILES的目录下。
18.16.7 配置Postfix
main.cf文件是Postfix的首要配置文件。master.cf文件配置服务器程序。它还定义了各种的查找表,供main.cf访问,并且提供不同类型的服务映射。
postconf(5)的手册页描述了能在main.cf文件中设定的每一个参数。如果您只是键入man postconf,那么看到的是postconf程序的手册页。使用man -s 5 postconf才能看到描述main.cf配置选项的手册版本。
Postfix配置语言看上去有点儿像是Bourne shell注释和赋值一类的语句。在变量前面加一个$,就可以在定义其他变量的时候引用它。变量定义就像它们出现在配置文件一样被保存下来,它们在使用的时候才扩展,此时要做全部的替换。
您可以通过给变量赋值的方式创建新变量。要小心选择不会和现有的配置变量冲突的名字。
包括查找表在内的所有的Postfix配置文件,都把以空白开头的行当作续行。这个约定能产生可读性很好的配置文件,但是您必须在第一列开始新行。
18.16.8 main.cf的内容
在main.cf文件里可以设置超过300个参数。不过,在一个普通的站点上,只需要设其中的几个即可,因为默认的设定几乎都做好了。Postfix的作者强烈建议您支配没有默认值的那些参数。那样一来,如果一个参数的默认值在将来发生了变化,那么您的配置就会自动采用新的默认值。
随Postfix软件发布带的main.cf文件样板包括了许多注释掉的参数例子,还有一些简要介绍。最好把原来的版本留着以供参考。从空文件开始设定您自己的配置,这样一来,您所做的设定就不会淹没在注释的汪洋大海里。
18.16.9 基本设置
让我们从一个尽可能简单的例子开始:一个空文件。吃惊吧,这个空文件也是一个完全合理的Postfix配置文件。它配置的邮件服务器往本地投递邮件(本地的意思是指与本地主机名在同一个域),以及把目的地不是本地地址的任何消息直接发给合适的远程服务器。
另一个简单的配置是一个“空客户机(null client)”,也就是说,一个系统不向本地投递任何电子邮件,只把对外的邮件转发给一台指定的中央服务器。对于这种配置,我们定义了几个参数,首先是mydomain,它定义了主机名的域名部分,其次是myorigin,它是给不全的电子邮件地址追加的邮件域。如果mydomain和myorigin两个参数相同,那么我们就可以写成下面这个样子:
mydomain = cs.colorado.edu
myorigin = $mydomain
我们应该设定的另一个参数是mydestination,它指定了本地的邮件域(也称为“规范”域)。如果一则消息的收件人地址把mydestination作为其邮件域,那么这则消息就要通过local程序投递给相应的用户(假定没有找到对应的别名或者.forward文件)。如果在mydestination里包含了一个以上的邮件域,那么这些域都被当作是同一个域的别名。
我们想要我们的空客户机(null client)不向本地投递邮件,所以这个参数应该为空:
mydestination =
最后,参数relayhost告诉Postfix把所有不是本地的消息发给一台指定的主机,而不是把它们直接发给它们表面上的目的地:
relayhost = [mail.cs.colorado.edu]
中括号告诉Postfix把指定的字符串当作一个主机名(DNS的A记录),而不是当作一个邮件域名(DNS的MX记录)。
既然空客户机不应该接收从其他系统发来的邮件,所以在空客户机的配置里最后要做的一件事情是,注释掉master.cf文件里的smptd一行。这个改动根本不让Postfix运行smtpd。只用这么简单的几行配置,我们就定义了一个功能完善的空客户机。
对于一台“真实的”邮件服务器来说,您不但多需要几个配置选项,而且还要映射表。我们在接下来的几小节里介绍这些表。
18.16.10 使用postconf
postconf是一个能帮助您配置Postfix的方便工具。不带参数运行该工具,它会打印出所有当前配置好的参数。如果您把一个特定参数作为postconf的命令参数,那么它就会打印出那个参数的值。-d选项能让postconf打印参数的默认值而不是当前配置的值。
例如:

另一个有用的选项是-n,它让postconf只打印当前配置值和默认值不同的参数。如果您要在Postfix的邮递列表上寻求帮助,那么您应该在自己的邮件里带上这个命令输出的配置信息。
18.16.11 查找表
Postfix操作的许多方面都是通过查找(lookup)表来决定的,这张表能把关键字映射到值,或者实现简单的列表。例如,alias_maps表的默认设定为:
alias_maps = dbm:/etc/mail/aliases, nis:mail.aliases
数据源用type:aliases这样的表示方法指定。注意,这个特殊的表实际上同时使用了两个不同的信息源:一个是dbm数据库,另一个是NIS映射。多个值用逗号、空格或者两者合起来进行分隔。表18.22列出了能用的数据源,postconf -m命令也能给出这一信息。
表18.22 可用作Postfix查找表的信息源
|
类 型 |
说 明 |
|
dbm /
sdbm |
传统的dbm或者gdbm数据库文件
|
|
cidr |
CIDR形式的网络地址 |
|
hash
/ btree |
Berkeley DB散列表(代替dbm)或者B-树表 |
|
ldap |
LDAP目录服务 |
|
mysql |
MySQL数据库 |
|
nis |
NIS目录服务 |
|
pcre |
兼容Perl的正则表达式 |
|
pgsql |
PostgreSQL数据库 |
|
proxy |
通过proxymap访问,例如逃出chroot |
|
regexp |
POSIX正则表达式 |
|
static |
不管关键字如何,都返回path指定的值 |
|
unix |
Linux的/etc/passwd和/etc/group文件,使用NIS句法a |
a.unix:passwd.byname是passwd文件,unix:group.byname是group文件。
使用dbm和gdbm类型只是为了和传统的sendmail别名表保持兼容。Berkeley DB(hash)是一种更为现代的实现;它更安全,速度也更快。如果兼容性不是一个问题的话,那么使用:
alias_database = hash:/etc/mail/aliases
alias_maps = hash:/etc/mail/aliases
alias_database指定由newaliases重建的表,它应该与您在alias_maps中指定的表相对应。有两个参数的原因是alias_maps可能包括非DB的信息源,例如mysql或者nis,它们不需要重建。
所有DB类的表(dbm、sdbm、hash和btree)都基于一个文本文件,这个文本文件可以编译成一个搜索效率高的二进制格式。在注释和续行方面,这些文本文件的句法都和配置文件的句法相似。文件中的配置项用简单的一对“关键字/值”来指定,两者间用空格分隔,对此例外的是别名表,为了保持与sendmail的兼容性,这个表必须在关键字之后有一个分号。例如,下面这几行适合于别名表:
postmaster: david, tobias
webmaster: evi
再举一个例子,下面是一个访问表,用于中继从任何一个主机名以cs.colorado.edu结尾的客户机来的邮件:
.cs.colorado.edu OK
对于这些文本文件来说,普通表用postmap命令编译成它们的二进制格式,而别名表则用postalias命令编译成二进制格式。表的说明(包括类型)必须用第一个参数指明,例如:
$ postmap hash:/etc/postfix/access
postmap还能查询一张查找表里的值:
$ postmap -q blabla hash:/etc/postfix/access
$ postmap -q .cs.colorado.edu hash:/etc/postfix/access
OK
18.16.12 本地投递
local程序负责把邮件投递到合乎规定的域。它还处理本地别名。例如,如果mydestination设为cs.colorado.edu,而收到一封发给evi@cs.colorado.edu的电子邮件,那么local首先查一次alias_maps表,然后递归地替换任何匹配项。
如果没有匹配的别名,那么local就找evi主目录里的.forward文件,如果有这个文件(句法和一个别名映射的右边一样),它就按照这个文件的指示处理邮件。最后,如果没有找到.forward文件,那么电子邮件就投递到evi的本地邮箱。
在默认情况下,local把电子邮件写到/var/mial下标准mbox格式的文件里。您可以用表18.23里的参数改变local的操作。
表18.23 local投递到本地邮箱采用的参数(在main.cf里设置)
|
参 数 |
说 明 |
|
mail_spool_directory |
把邮件投递到一个服务所有用户的中央目录 |
|
home_mailbox |
把邮件投递到指定的相对路径下的~usr |
|
mailbox_command |
用一个外部程序投递邮件,一般是procmail |
|
mailbox_transport |
通过master.cf里定义的一个服务投递邮件a |
|
recipient_delimiter |
允许使用扩展的用户名(参见下面的介绍) |
a.这个选项和邮箱服务器程序(例如Cyrus的impad)交互。
mail_spool_directory和home_mailbox这两个参数正常情况下会生成mbox格式的邮箱,但是它们也能生成qmail风格的Maildir邮箱。在路径名末尾加一个斜线,就能按这个方式生成邮箱。
如果recipient_delimiter被设为+,那么可以接受发到evi+whatever@cs.colorado.edu这个地址的邮件,它被投递给evi的账号。有了这个功能,用户就能创建特殊用途的地址,并且按目的地址来对它们进行分类。Postfix首先按完整地址来查找,只有在找不到的情况下,它才去掉扩展的部分,退而使用基本地址。Postfix还能查找相应的转发文件.forward+whatever做更进一步的别名替换。
18.16.13 虚拟域
如果您想要在Postfix邮件服务器上托管一个邮件域,那么您可以有3种选择。
在mydestination里列出这个域。邮件按照前面介绍的那样来投递:做别名扩展,把邮件投递给相应的用户。
在参数virtual_alias_domains里列出这个域。这个选项让这个域有自己的独立寻址空间,和系统的用户账号没有关系。在这个域中的所有地址都必须能解析成(通过映射)它之外的真实地址。
在参数virtual_mailbox_domains里列出这个域。和virtual_alias_domains选项一样,这个域有自己的名字空间。不过,邮件可以投递到一个指定的目录下的所有邮箱里,和用户账号没有关系。
在上述3个地方之一列出这个域。请仔细选择,因为许多配置元素都要依靠这个选择。我们已经看过mydestination方法的处理。其他几个选项在下面讨论。
18.16.14 虚拟别名域
如果一个域列为virtual_alias_domains参数的值,那么Postfix就接受发到那个域的邮件,而且必须把这些邮件转发到本地机器或者别处的一个实际的收件人那里。
对虚拟域里地址的转发功能必须在参数virtual_alias_maps里包括的一张查找表里定义。这张表里的表项,左边是虚拟域,右边是实际的目的地址。
右边域名不全的名字被解释为本地主机上的一个用户名。
考虑下面这个从main.cf里截取的例子:

于是在/etc/mail/admin.com/virtual里,我们会有下面几行:

发给evi@admin.com的邮件都被重新定向到evi@cs.colorado.edu(追加了myorigin),最终投递到evi用户的邮箱,因为mydestination里包括cs.colorado.edu。
定义可以递归:右边可以包括在左边再次被定义的地址。注意,右边只可以是一个地址的列表。如果您需要执行一个外部程序,或者要使用:include:文件,那么要把这个电子邮件重定向为一个别名,接着就可以按照您的需要对别名进行扩展。
为了把所有内容都放在一个文件里,您可以把virtual_alias_domains设为和virtual_alias_
maps一样的查找表,并且在这张表里加一个特殊项,把它标为一个虚拟的别名域。在main.cf文件里:
virtual_alias_domains = $virtual_alias_maps
virtual_alias_maps = hash:/etc/mail/admin.com/virtual
在/etc/mail/admin.com/virtual里:
admin.com notused
postmaster@admin.com evi, david@admin.com
…
给邮件域(admin.com)的配置项的右边实际上从来都不用,该表中存在独立的admin.com一项就足以让Postfix把它当作一个虚拟的别名域。
18.16.15 虚拟邮箱域
在virtual_mailbox_domains下列出的域和本地(规范)域类似,但是用户的列表及其对应的邮箱必须独立于系统的用户账号进行管理。
参数virtual_mailbox_maps指向了一张表,其中列出了该域内所有的有效用户名。这个映射表的格式为:
user@domain /path/to/mailbox
如果这一路径以斜线结尾,那么邮箱就保存为Maildir格式。virtual_mailbox_base的值一定以这个指定的路径开头。
您经常可能想要给virtual_mailbox_base中的地址起个别名。使用virtual_alias_map就能达到上述目的。下面是一个完整的例子。
在main.cf中的配置为:

/etc/mail/admin.com/vmailboxes可能包括类似下面这样的项:
