使用 Postfix、OpenLDAP 和 Courier 的大规模邮件系统

作者:Dave Dribin

虽然本文提供了关于使用 Postfix、OpenLDAP 和 Courier-IMAP 设置集成邮件服务器的说明,但它没有讨论如何选择这些软件组件,这本身可能就是一篇完整的文章。目标是在单台机器上为多个域设置 SMTP 邮件服务器,并通过 IMAP 进行远程访问。此外,我们希望拥有不对应 shell 帐户的 IMAP 帐户,而不是仅将邮件传递给拥有 shell 帐户的人。这产生了两种类型的帐户:本地帐户和虚拟帐户。本地帐户是那些具有 shell 访问权限的帐户。他们使用其 shell 用户名和密码来访问 IMAP。虚拟帐户具有仅用于登录 IMAP 的用户名和密码。“本地”和“虚拟”这两个术语贯穿本文的其余部分。

概览

图 1 显示了 Postfix、Courier、Procmail 和 OpenLDAP 如何交互。本地帐户信息存储在 /etc/password 中,身份验证由可插拔身份验证模块 (PAM) 处理。虚拟帐户信息存储在 LDAP 目录中。LDAP 提供帐户查找和身份验证功能。可以避免使用 LDAP 目录,但管理虚拟帐户信息将更加困难。例如,Postfix 和 Courier 都支持使用配置文件的虚拟帐户,但它们的文件格式不同。

Postfix 接受来自 SMTP 的传入邮件。它将拒绝任何未知帐户(包括本地帐户和虚拟帐户)的邮件。它自己传递虚拟帐户的邮件,并使用 Procmail 作为本地帐户的 MDA。Courier 通过 IMAP 和 POP 协议提供对邮箱的远程访问。

Large-Scale Mail with Postfix, OpenLDAP and Courier

图 1. 总体设计

邮箱位置

本地帐户的邮件以 Maildir 格式存储在其主目录 ${HOME}/Maildir/ 中。Maildir 传递的标准做法是将邮件放入帐户的主目录,而不是 /var/spool/mail。Postfix 和 Courier 都开箱即用地支持此标准行为。

与本地帐户不同,虚拟帐户的电子邮件没有标准位置。我们创建了一个名为 vmail 的单个 UNIX 帐户,用于保存所有虚拟帐户的邮件。每个虚拟域在 ~vmail/domains/ 目录中都有一个子目录。例如,如果有一个帐户 <john@example.com>,则邮件将以 maildir 格式存储在 ~vmail/domains/example.com/john/ 中。您也可以将虚拟帐户分散到多个 UNIX 帐户中,例如,为每个虚拟域创建一个 UNIX 帐户。

LDAP 目录设计

在设计目录时,有很多可能性,本文并未涵盖此主题的所有方面。一个有用的参考资料是 iPlanet 部署指南(请参阅“资源”)。本文假设您熟悉 LDAP 概念和术语。您应该预先花时间设计一个符合您特定需求的树结构。

树结构

图 2 显示了一家网络托管公司的示例目录树。公司的域名 myhosting.example 被选为根后缀。Postfix 和 Courier 都搜索 o=hosting,dc=myhosting,dc=example 子树以查找电子邮件信息。o=accounts,dc=myhosting,dc=example 子树显示了如何将 PAM 的 shell 帐户信息集成到同一目录中,但这对于设置电子邮件不是必需的。每个托管域都在托管组织下获得自己的组织。每个电子邮件帐户都位于域的子树下。因此,<user2@domain2.example> 电子邮件地址的专有名称是

mail=<user2@domain2.example>,o=domain2.example,
 o=hosting,dc=myhosting,dc=example

这是一个相当稳定的设计,因为帐户永远不会在域之间转移。最终结果是良好的 LDAP 设计,因为在 LDAP 中移动子树可能会很麻烦。该设计也非常灵活,因为每个域的树结构都可以根据需要进行定制。每个域都必须有一个提供双重功能的 postmaster 条目。其主要功能是访问控制,但它也充当转发电子邮件地址。每个域还必须有一个 abuse 别名,将邮件转发给系统管理员。

Large-Scale Mail with Postfix, OpenLDAP and Courier

图 2. 网络托管公司的示例目录树

选择模式

模式通过定义对象类来定义条目可以具有哪些属性。OpenLDAP 附带的默认模式都不真正适用于专门用于电子邮件邮箱或转发的条目。我们正在使用 Courier 在其发行版中提供的模式。另一个可以查看的模式是 qmail-LDAP 项目发布的模式。您也可以设计自己的模式,但请注意,您应该使用在 Internet Assigned Numbers Authority (IANA) 注册的 OID。

Courier 模式

表 1 中总结的 courierMailAccount 对象类用于虚拟电子邮件帐户。表 2 中总结的 courierMailAlias 对象类用于转发到另一个地址的电子邮件地址。

表 1. courierMailAccount

表 2. courierMailAlias

courierMailAccount 对象类并不完全符合我们的需求。我们不需要 uidNumber 和 gidNumber,因为所有邮件都发送到 vmail 帐户。但是,我们必须放入虚拟值,因为模式要求它们。请注意,如果我们将虚拟帐户分散到多个 UNIX 帐户中,这些值将是有意义的。我们需要 mailbox 属性,因为它需要确定文件系统上邮箱的位置。邮箱必须以斜杠结尾,以指示它是 Maildir 样式的邮箱。userPassword 属性也是必需的,因为所有电子邮件帐户都必须具有密码才能通过 IMAP 或 POP 访问。我们不使用其他可选属性。

courierMailAlias 对象类非常适合我们的需求。我们仅使用两个必需的属性,而不使用任何可选属性。maildrop 属性可以是另一个电子邮件地址或此计算机上的本地帐户。

访问控制

OpenLDAP 提供了许多访问控制的可能性。默认情况下,root 帐户对树中的所有条目都具有读写访问权限。我们希望将其中一些管理权限委派给每个托管域中的各个帐户,以便他们可以在不访问 root 帐户的情况下自行进行少量更改。这是通过将 postmaster 条目设为 organizationalRole,并为每个具有管理权限的条目设置 roleOccupant 属性来完成的。然后可以将 OpenLDAP 配置为仅允许对此组成员进行访问。

实施

本节介绍如何实施虚拟邮件解决方案。并非涵盖每个小细节,仅涵盖标准安装之外所需的内容。

以下是软件列表及其版本号,我们使用这些版本号测试了此配置

  • Red Hat Linux 6.2、7.1 或 7.2

  • Postfix 1.1.x

  • OpenLDAP 2.0.21

  • Courier-IMAP 1.4.1

  • Procmail 3.22

您需要创建 vmail 帐户,然后创建 ~/vmail/domains/ 目录。您还需要按照 Postfix 的 INSTALL 文档中的说明,为 Postfix 创建一个帐户和两个组。

您无需遵循任何特殊说明来编译和安装 OpenLDAP,因此请查阅其文档以获取说明。对于生产环境,请阅读有关以非 root 帐户运行 OpenLDAP、设置 chroot 环境和复制的信息。本文介绍如何为单服务器配置 slapd,创建基本树结构并将一些基本数据插入 LDAP 目录。图 2 显示了我们在此处设置的 LDAP 树。

配置 slapd

您需要使 Courier 的模式文件可用,因此请将 Courier 发行版中 authlib/authldap.schema 文件复制到 /usr/local/etc/openldap/schema/courier.schema。Courier 的模式依赖于 cosine.schema 和 nis.schema。将以下行添加到 slapd.conf

include   /usr/local/etc/openldap/schema/cosine.schema
include   /usr/local/etc/openldap/schema/nis.schema
include   /usr/local/etc/openldap/schema/courier.schema

接下来,在 slapd.conf 中使用以下行设置数据库定义

database      ldbm
directory     /usr/local/var/openldap-ldbm
suffix        "dc=myhosting,dc=example"
database 指令指定要使用的后端类型(使用 LDBM 作为后端数据库)。directory 指令指定 LDBM 数据库的路径。确保指定的目录在启动 slapd 之前存在,并且 slapd 对该目录具有读写权限。suffix 指令指定此数据库的根后缀。接下来的几行设置超级用户或 root 帐户
rootdn       "cn=Manager,dc=myhosting,dc=example"
rootpw       {SSHA}ra0sD47QP32ASAlaAhF8kgi+8Aflbgr7
rootdn 条目具有对数据库的完全访问权限,这就是为什么密码存储在实际数据库之外的原因。rootpw 中的密码应始终以哈希格式存储。不要以明文形式存储密码。要将明文密码 secret 转换为哈希格式,请使用 slappasswd 命令
% slappasswd
New password: secret
Re-enter new password: secret
{SSHA}ra0sD47QP32ASAlaAhF8kgi+8Aflbgr7
从 slappasswd 获取输出,并将其复制到 slapd.conf 中,就像我们上面所做的那样。

为了加快搜索速度,您应该为常用搜索属性创建索引

index   objectClass  pres,eq
index   mail,cn      eq,sub

slapd.conf 的最后一部分是访问控制。OpenLDAP FAQ 包含有关如何将 postmaster 设置为组 ACL 的良好信息。

创建目录树

现在 slapd 已配置完毕,是时候开始向 LDAP 目录添加数据了。我们使用 OpenLDAP 附带的命令行工具并创建 LDIF 文件来修改目录。

第一步是使用我们的根节点、托管组织和 rootdn 的条目创建基本树结构。创建一个名为 base.ldif 的文件,内容如下

dn: dc=myhosting, dc=example
 objectClass: top
 dn: cn=Manager, dc=myhosting, dc=example
 objectClass: top
 objectClass: organizationalRole
 cn: Manager
 dn: o=hosting, dc=myhosting, dc=example
 objectClass: top
 objectClass: organization
 o: hosting

现在使用 ldapadd,以 root 帐户绑定,添加此 LDIF

ldapadd -x -D "cn=Manager,dc=myhosting,dc=example" \
-w secret -f base.ldif
添加域

现在可以在托管树下添加域。每个域至少需要具有 postmaster 和 abuse 条目。要为 domain1.example 创建树,请创建一个名为 domain1.example.ldif 的文件,内容如下

dn: o=domain1.example, o=hosting, dc=myhosting,
 dc=example
objectClass: top
objectClass: organization
o: domain1.example
dn: cn=postmaster, o=domain1.example, o=hosting,
 dc=myhosting, dc=example
objectClass: top
objectClass: organizationalRole
objectClass: CourierMailAlias
cn: postmaster
mail:
maildrop: postmaster
dn: mail=abuse@domain1.example, o=domain1.example,
 o=hosting, dc=myhosting, dc=example
objectClass: top
objectClass: CourierMailAlias
mail:
maildrop: abuse

请注意,maildrop 属性是本地电子邮件帐户,并将转发到 /etc/aliases 中的 postmaster 和 abuse 帐户。postmaster 角色中没有帐户,因此目前只有 root 帐户可以创建帐户。使用以下命令添加此域

ldapadd -x -D "cn=Manager,dc=myhosting,dc=example"
\
-w secret  -f domain1.example.ldif
添加帐户

现在,让我们添加一个电子邮件为 <user1@domain1.example> 的帐户。我们还为该帐户授予 domain1.example 的 postmaster 权限。创建一个 user1.domain1.example.ldif,内容如下

dn: mail=user1@domain1.example, o=domain1.example,
 o=hosting, dc=myhosting, dc=example
objectClass: top
objectClass: CourierMailAccount
mail:
homeDirectory: /home/vmail/domains
uidNumber: 101
gidNumber: 101
mailbox: domain1.example/user1
dn: cn=postmaster, o=domain1.example, o=hosting,
dc=myhosting, dc=example
changetype: modify
add: roleOccupant
roleOccupant: mail=user1@domain1.example,
 o=domain1.example, o=hosting,
 dc=myhosting, dc=example

第一部分为帐户添加新条目。主目录和邮箱指向文件系统上的物理邮箱。uidNumber 和 gidNumber 属性是必需的,但未使用,因此使用虚拟值 101 填充。第二部分通过添加带有 user1@domain1.example 的 DN 的 roleOccupant 属性来修改 postmaster 条目。让我们创建此帐户

ldapadd -x -D "cn=Manager,dc=myhosting,dc=example"
\
-w secret -f user1.domain1.example.ldif
该帐户尚未设置密码,因此即使已授予其 postmaster 权限,也无法进行身份验证。使用 ldappasswd 命令将初始密码设置为 user1
ldappasswd -x -D "$DN" -w $PW -s user1 \
"mail=user1@domain1.example, o=domain1.example,
o=hosting, dc=myhosting, dc=example"
可以使用类似的 LDIF 文件添加其他域和帐户。手动创建 LDIF 文件可能很麻烦且容易出错。我们稍后将讨论用于管理的替代方案。
Postfix

我们仅介绍 Postfix 中与邮件托管相关的部分。要处理 Postfix 设置的其他部分,请访问 Postfix 网页。

下载 Postfix 源代码并解压。您需要重建 Postfix Makefile,以便了解 LDAP 并链接到它。为此,请执行以下命令

make makefiles CCARGS="-I/usr/local/include
-DHAS_LDAP" AUXLIBS="-L/usr/local/lib -lldap
-L/usr/local/lib -llber"

此时,请按照其 INSTALL 和 LDAP_README 文件中记录的正常 Postfix 编译和安装说明进行操作。

配置 Postfix

在为此任务配置 Postfix 时,我们主要关注 /etc/postfix/main.cf。对于大多数 Postfix 配置,您将以最适合您站点的方式进行配置,您可以遵循 Postfix 源代码或 Postfix 网站上包含的文档。在这里,我们讨论受此设置影响的设置。如果以下显示的任何配置示例未明确归因于特定文件,则假定它们可以在 main.cf 中找到。

传输表将域映射到消息传递传输(如 /etc/postfix/master.cf 中指定)和/或中继主机。对于我们的虚拟域,我们希望将它们映射到 Postfix 附带的虚拟传递代理。传输表可能如下所示

domain1.example           virtual:
domain2.example           virtual:

在以纯文本形式制作传输表后,您需要使用 postmap 将其制作为二进制 DB 文件(请参阅 man postmap)。此时,告诉 Postfix 有一个传输表以及在哪里找到它。您还需要让 Postfix 知道我们接受这些域的邮件。这通过 transport_maps 和 mydestination 指令完成

transport_maps = hash:/etc/postfix/transport
mydestination = $myhostname, localhost.$mydomain,
  $mydomain, $transport_maps
您可以轻松定义多个 LDAP 源。LDAP 源参数记录在 Postfix 源代码的 README_FILES/LDAP_README 中。参数名称遵循 <ldapsource>_parameter 的模式。LDAP 源名称由 use 定义。在 main.cf 中,每个查找都需要一个 LDAP 源定义。
别名

第一个 LDAP 源定义用于虚拟别名。我们将此 LDAP 源命名为 aliases。在我们的配置中,我们的 LDAP 服务器在 localhost 上运行。搜索库是我们 LDAP 服务器中定义的托管子树的顶部。我们正在查询邮件元素与电子邮件收件人匹配以及属于 courierMailAlias 对象类的项目。别名的目标存储在 maildrop 属性中。Postfix 不会使用帐户绑定,而是会执行匿名查找

aliases_server_host = localhost
aliases_search_base =
  o=hosting,dc=myhosting,dc=example
aliases_query_filter =
  (&(mail=%s)(objectClass=CourierMailAlias))
aliases_result_attribute = maildrop
aliases_bind = no
帐户

当使用 accounts 源时,我们正在查找具有 courierMailAccount 对象类的条目。我们请求 mailbox 属性作为结果

accounts_server_host = localhost
accounts_search_base =
  o=hosting,dc=myhosting,dc=example
accounts_query_filter =
  (&(mail=%s)(objectClass=CourierMailAccount))
accounts_result_attribute = mailbox
accounts_bind = no

还需要定义第二个帐户源 accountsmap,以帮助在使用通配符时定位帐户。如果没有此查找,别名中的通配符将覆盖域中的虚拟帐户

accountsmap_server_host = localhost
accountsmap_search_base = o=hosting,dc=myhosting,dc=example
accountsmap_query_filter =
(&(mail=%s)(objectClass=CourierMailAccount
accountsmap_result_attribute = mail
accountsmap_bind = no
现在别名和 accountsmap LDAP 源已定义,让 Postfix 知道通过在 main.cf 中定义 virtual_maps 参数来使用它
virtual_maps = ldap:aliases
对于此示例,假设已创建一个 vmail UNIX 帐户,其 UID 为 125,GID 为 120,并且其主目录为 /home/vmail
:virtual_mailbox_base = /home/vmail/domains
virtual_mailbox_maps = ldap:accounts
virtual_minimum_uid = 125
virtual_uid_maps = static:125
virtual_gid_maps = static:120
将 virtual_uid_maps 和 virtual_gid_maps 设置为特殊的静态映射,并将其硬编码为 vmail 帐户的 UID 和 GID。此处显示的所有参数都在 README_FILES/VIRTUAL_README 中完全记录,该文件随 Postfix 源代码一起提供。

我们还需要编辑 local_recipient_maps 参数以查看 virtual_mailbox_maps,以便 Postfix 知道哪些帐户是有效的。这是必要的,以便 Postfix 可以拒绝未知帐户的邮件

local_recipient_maps = $alias_maps
  unix:passwd.byname $virtual_mailbox_maps
Courier

安装 Courier 没有特殊说明,因此请参阅其文档以获取完整说明。它应该自动检测 LDAP 并将其构建到其中。您应认真考虑将 --enable-workarounds-for-imap-client-bugs 选项传递给 ./configure,否则 Netscape 邮件用户可能在与您的服务器交互时遇到问题。这稍微弯曲了 IMAP 协议,但拥有快乐的用户比拥有完美的协议和不开心的用户更好。

Courier 使用身份验证守护程序来使身份验证与其他系统部分分离。配置它,以便在 LDAP 或 PAM 中找到有效的电子邮件帐户。使用 authmodulelist 参数在 authdaemonrc 中指定此项

authmodulelist="authldap authpam"

所有 LDAP 参数都在 authldaprc 中。大多数参数都是不言自明的。但是,要使用 Courier 模式,您实际上需要进行一些修改。您还需要将所有虚拟帐户映射到 vmail 帐户。以下是您需要对 authldaprc 进行的更新摘要

LDAP_GLOB_UID           vmail
LDAP_GLOB_GID           vmail
LDAP_HOMEDIR            homeDirectory
LDAP_MAILDIR            mailbox
LDAP_CRYPTPW            userPassword
需要关注的其他三个设置是 LDAP_AUTHBIND、LDAP_BINDDN 和 LDAP_BINDPW。这些与用户身份验证有关。LDAP_AUTHBIND 与 LDAP_BINDDN 和 LDAP_BINDPW 互斥。我们建议使用 LDAP_AUTHBIND。authldaprc 中的注释提到了在使用 LDAP_AUTHBIND 时 OpenLDAP 中存在内存泄漏,但在 OpenLDAP 版本 2.0.19 中已修复。

如果您使用 LDAP_BINDDN 和 LDAP_BINDPW,则密码仅限于 crypt、MD5 和 SHA 算法。SMD5 和 SSHA 不可用。此外,在定义 LDAP_BINDPW 时,您必须将 root LDAP 密码以明文形式放入 authldaprc 中。以明文形式放入 root LDAP 密码存在安全问题,因此如果可以,请务必使用 LDAP_AUTHBIND。

最后一个更改是通过将 IMAPDSTART 参数设置为 YES 来启用 IMAP 服务器。您现在应该能够使用 courier-imap.sysvinit 启动脚本来启动和停止 IMAP 守护程序。

管理

大多数管理任务(例如添加、修改和删除帐户和别名)都需要修改 LDAP 目录。您可以使用 OpenLDAP 命令行工具或通用 LDAP 浏览器(如 gq)来执行此操作。但是,这些方法很麻烦,因为它们是通用工具,并非专门用于管理电子邮件帐户的任务。我们一直在开发一个名为 Jamm 的 Web 管理应用程序,它本质上是用 Java 和 JSP 编写的特定于应用程序的 LDAP 浏览器。它也有自己的 LDAP 模式,该模式是稍作修改的 Courier 模式。Jamm 目前可用并且不断发展。请访问 SourceForge 上的 Jamm 网页以获取最新的 Jamm 信息。

帐户创建注意事项

当您在 LDAP 数据库中创建帐户或别名时,就邮件系统而言,它将立即变为活动状态。对于虚拟帐户,请注意,此时不会创建 ~vmail 中的 UNIX 目录。但是,我们可以解决这个问题,因为 Postfix 的虚拟传递代理会在第一次必须传递邮件时创建必要的目录。由于这个事实,我们建议在创建帐户后立即发送欢迎电子邮件。

帐户删除注意事项

当您在 LDAP 数据库中删除帐户或别名时,它将立即变为非活动状态。对于虚拟帐户,请注意,UNIX 文件系统未清理。换句话说,数据将保留在磁盘上,直到系统管理员可以将其删除。这样,您可以将已失效帐户的数据保留一段宽限期,以防帐户被错误删除。但是,如果使用相同的名称和相同的邮件路径创建另一个帐户,则新帐户可以使用该数据。这可能被视为侵犯了前用户的隐私。

资源

Large-Scale Mail with Postfix, OpenLDAP and Courier
Dave Dribin (dave@dribin.org) 自 1991 年以来一直使用 UNIX,自 1993 年以来一直使用 Linux。自 1995 年以来,他一直在专业地为 UNIX 或在 UNIX 上开发软件。Dave 目前在 National Association of Realtors 担任独立顾问。

Large-Scale Mail with Postfix, OpenLDAP and Courier
Keith Garner 自 1994 年 1 月以来一直使用 Linux。自 1997 年以来,他一直在专业地管理和开发 UNIX 软件。Keith 目前受雇于 National Association of Realtors。
加载 Disqus 评论