Postfix 对 LDAP 的支持
Postfix 可以将 LDAP 目录用作其任何查找操作的来源:aliases(5),virtual(5),canonical(5) 等。这允许您将邮件服务的相关信息存储在具有精细访问控制的复制网络数据库中。由于未将这些信息本地存储在邮件服务器上,管理员可以从任何位置进行维护,而用户可以控制您认为合适的任何部分。您可以让多个邮件服务器使用相同的信息,而无需经历将信息复制到每个服务器的麻烦和延迟。
本文件涵盖的主题:
使用 LDAP 支持构建 Postfix
这些说明假设您已按照 INSTALL 文档从源代码构建了 Postfix。如果您从供应商特定的源代码包构建 Postfix,可能需要进行一些修改。
注 1:Postfix 不再支持 LDAP 版本 1 接口。
注 2:要在 Debian GNU/Linux 的 Postfix 中使用 LDAP,只需安装 postfix-ldap 包即可。无需重新编译 Postfix。
您需要在系统中安装 LDAP 库和头文件,并相应地配置 Postfix 的 Makefile。
例如,要为 Postfix 构建 OpenLDAP 库(即仅 LDAP 客户端代码),您可以使用以下命令:
% ./configure --without-kerberos --without-cyrus-sasl --without-tls \ --without-threads --disable-slapd --disable-slurpd \ --disable-debug --disable-shared
如果您使用的是 UM 发行版的库(http://www.umich.edu/~dirsvcs/ldap/ldap.html)或 OpenLDAP(http://www.openldap.org)的库,则在 Postfix 源代码树的顶层添加以下内容应可正常工作:
% make tidy % make makefiles CCARGS="-I/usr/local/include -DHAS_LDAP" \ AUXLIBS_LDAP="-L/usr/local/lib -lldap -L/usr/local/lib -llber"
如果 LDAP 共享库位于运行时链接器无法识别的目录中,请在 "-lldap" 之后添加 "-Wl,-R,/path/to/directory" 选项。
Postfix 3.0 之前的版本使用 AUXLIBS 而不是 AUXLIBS_LDAP。在 Postfix 3.0 及更高版本中,旧的 AUXLIBS 变量仍支持构建静态加载的 LDAP 数据库客户端,但仅新的 AUXLIBS_LDAP 变量支持构建动态加载或静态加载的 LDAP 数据库客户端。
未使用 AUXLIBS_LDAP 变量将导致动态数据库客户端加载失效。每个 Postfix 可执行文件都将依赖 LDAP 数据库库。而动态数据库客户端加载的初衷正是为了避免这种依赖。
在 Solaris 2.x 上,您可能需要指定运行时链接信息,否则 ld.so 将无法找到某些共享库:
% make tidy % make makefiles CCARGS="-I/usr/local/include -DHAS_LDAP" \ AUXLIBS_LDAP="-L/usr/local/lib -R/usr/local/lib -lldap \ -L/usr/local/lib -R/usr/local/lib -llber"
'make tidy' 命令仅在之前未包含 LDAP 支持构建 Postfix 时需要。
请将 '/usr/local' 替换为 LDAP 包含文件和库的实际路径。请确保不同版本的 LDAP 包含文件和库不会混用!!
如果 LDAP 库在编译时启用了 Kerberos 支持,还需在此行中包含 Kerberos 库。注意:KTH Kerberos IV 库可能与 Postfix 的 lib/libdns.a 冲突,后者定义了 dns_lookup 函数。如果发生冲突,您可能需要链接不含 Kerberos 支持的 LDAP 库来构建 Postfix,因为 Postfix 本身不支持对 LDAP 服务器进行 Kerberos 绑定。对此带来的不便表示歉意。
如果您使用的是 Netscape LDAP SDK,您需要将 AUXLIBS 行修改为指向 libldap10.so 或 libldapssl30.so(或您实际使用的库文件),并可能需要使用适当的链接器选项(例如 '-R'),以便可执行文件在运行时能够找到该库。
如果您使用的是 OpenLDAP,且库文件已编译时启用了 SASL 支持,可以在 CCARGS 中添加 -DUSE_LDAP_SASL 以启用 SASL 支持。例如:
CCARGS="-I/usr/local/include -DHAS_LDAP -DUSE_LDAP_SASL"
配置 LDAP 查找
要使用 LDAP 查找,请在 main.cf 中将 LDAP 源定义为表查找,例如:
alias_maps = hash:/etc/aliases, ldap:/etc/postfix/ldap-aliases.cf
文件 /etc/postfix/ldap-aliases.cf 可以指定大量参数,包括启用 LDAP SSL 或 STARTTLS 以及 LDAP SASL 的参数。有关完整描述,请参阅 ldap_table(5) 手册页。
示例:local(8) 别名
以下是一个使用 LDAP 查找 local(8) 别名的基本示例。假设在 main.cf 中,您有:
alias_maps = hash:/etc/aliases, ldap:/etc/postfix/ldap-aliases.cf
并在 ldap:/etc/postfix/ldap-aliases.cf 中有:
server_host = ldap.example.com search_base = dc=example, dc=com
当收到发送到本地地址"ldapuser"但未在/etc/aliases数据库中找到的邮件时,Postfix将搜索监听在ldap.example.com端口389上的LDAP服务器。它将以匿名方式绑定,搜索所有具有 mailacceptinggeneralid 属性为 "ldapuser" 的目录条目,读取这些条目的 "maildrop" 属性,并构建一个包含这些 maildrop 的列表,这些地址将被视为 RFC822 地址,邮件将被投递到这些地址。
示例:虚拟域/地址
如果你希望在目录中保留虚拟查找的信息,只需稍作修改。首先,确保 Postfix 知道虚拟域。一种简单的方法是将域添加到目录中某个条目的 mailacceptinggeneralid 属性中。接下来,您需要确保所有虚拟收件人的 mailacceptinggeneralid 属性都包含其虚拟域名的完整名称。最后,如果您想将某个目录条目设为虚拟域名的默认用户,只需为其添加一个额外的 mailacceptinggeneralid(或目录中的等效设置)为 "@fake.dom"。没错,用户部分可以省略。如果您不希望使用默认用户,请跳过此步骤,域中发送到未知用户的邮件将直接被退回。
总结来说,您可能为虚拟域设置了一个默认用户,其配置如下:
dn: cn=defaultrecipient, dc=fake, dc=dom objectclass: top objectclass: virtualaccount cn: defaultrecipient owner: uid=root, dc=someserver, dc=isp, dc=dom 1 -> mailacceptinggeneralid: fake.dom 2 -> mailacceptinggeneralid: @fake.dom 3 -> maildrop: [email protected]
1: Postfix 在查找此条目并返回结果(maildrop)时,知道 fake.dom 是有效的虚拟域。
2: 这导致 fake.dom 中未知用户的邮件被发送到此条目 ...
3: ... 然后发送到其 maildrop。
普通用户可能只需一个邮件接受通用标识符和 maildrop,例如 "[email protected]" 和 "[email protected]"。
示例:扩展 LDAP 组
LDAP 常用于存储组成员信息。处理 LDAP 组的方法多种多样。我们将按复杂度从低到高展示几个示例,但由于独立变量过多,我们只能呈现解决方案空间的一小部分。我们将展示如何:
- 以地址列表形式查询组;
- 以包含地址的用户对象列表形式查询组;
- 将特殊列表未展开转发至独立列表服务器,用于审核或其他处理;
- 通过控制展开并特殊处理叶节点,利用Postfix 2.4新增功能处理复杂模式。
下面的示例 LDAP 条目和隐含模式展示了两个组条目("agroup" 和 "bgroup")以及四个用户条目("auser"、"buser"、"cuser" 和 "duser")。组"agroup"通过多值属性"memberdn"中的DN引用包含用户"auser"(1)和"buser"(2),并通过多值属性"memberaddr"存储两个外部用户的直接电子邮件地址"[email protected]"(3)和"[email protected]"(4)。"bgroup"和"cuser"/"duser"(6)/(7)/(8)/(9)的情况相同,但"bgroup"还具有"maildrop"属性,值为"[email protected]"(5):
dn: cn=agroup, dc=example, dc=com objectclass: top objectclass: ldapgroup cn: agroup mail: [email protected] 1 -> memberdn: uid=auser, dc=example, dc=com 2 -> memberdn: uid=buser, dc=example, dc=com 3 -> memberaddr: [email protected] 4 -> memberaddr: [email protected]
dn: cn=bgroup, dc=example, dc=com objectclass: top objectclass: ldapgroup cn: bgroup mail: [email protected] 5 -> maildrop: [email protected] 6 -> memberdn: uid=cuser, dc=example, dc=com 7 -> memberdn: uid=duser, dc=example, dc=com 8 -> memberaddr: [email protected] 9 -> memberaddr: [email protected]
dn: uid=auser, dc=example, dc=com objectclass: top objectclass: ldapuser uid: auser 10 -> mail: [email protected] 11 -> maildrop: [email protected]
dn: uid=buser, dc=example, dc=com objectclass: top objectclass: ldapuser uid: buser 12 -> mail: [email protected] 13 -> maildrop: [email protected]
dn: uid=cuser, dc=example, dc=com objectclass: top objectclass: ldapuser uid: cuser 14 -> mail: [email protected]
dn: uid=duser, dc=example, dc=com objectclass: top objectclass: ldapuser uid: duser 15 -> mail: [email protected]
我们的第一个用例忽略了"memberdn"属性,并假设组仅包含直接的"memberaddr"字符串,如(3)、(4)、(8)和(9)所示。目标是将组地址映射到构成该组的"memberaddr"值列表。这很简单,忽略各种与连接相关的设置(主机、端口、绑定设置、超时等),我们有:
simple.cf: ... search_base = dc=example, dc=com query_filter = mail=%s result_attribute = memberaddr $ postmap -q [email protected] ldap:/etc/postfix/simple.cf \ [email protected],[email protected]
我们搜索 "dc=example, dc=com"。查询过滤器中使用的 "mail" 属性用于定位正确的组,而 "result_attribute" 设置(如 ldap_table(5) 中所述)用于指定从匹配组中返回的 "memberaddr" 值以逗号分隔的列表形式。在将配置文件部署到生产环境中的main.cf之前,请务必使用postmap(1)命令并添加"-q"选项检查配置文件。
我们的第二个用例则扩展 "memberdn" 属性(1)、(2)、(6)和(7),跟随 DN 引用并返回被引用用户条目的 "maildrop"。在此我们使用ldap_table(5)中的"special_result_attribute"设置,将"memberdn"属性指定为存储所需成员条目DN的属性。"result_attribute"设置用于选择从选定DN中返回的属性。选择结果属性时,必须确保该属性不存在于组对象中,因为结果属性会从组和成员 DN 中同时收集。在此示例中,我们选择 "maildrop",并假设组对象从不包含 "maildrop" 属性("bgroup" 的 "maildrop" 属性用于其他场景)。示例数据中 "auser" 和 "buser" 的返回数据分别来自(11)和(13)项。
special.cf: ... search_base = dc=example, dc=com query_filter = mail=%s result_attribute = maildrop special_result_attribute = memberdn $ postmap -q [email protected] ldap:/etc/postfix/special.cf \ [email protected],[email protected]
注意:如果所需的成员对象结果属性在组中也始终存在,则会得到意外结果:展开操作还会返回组的地址。这是 Postfix 2.4 之前的版本中已知的限制,已在 Postfix 2.4 中通过新功能"leaf_result_attribute"解决,详情请参阅 ldap_table(5)。
我们的第三个用例中,部分组会被立即展开,而其他组则会被转发到专用的邮件列表管理主机进行延迟展开。这使用了两个 LDAP 表,一个用于用户和转发的组,另一个用于可以立即展开的组。假设需要转发的组从未作为直接展开组的嵌套成员存在。
no_expand.cf: ... search_base = dc=example, dc=com query_filter = mail=%s result_attribute = maildrop expand.cf ... search_base = dc=example, dc=com query_filter = mail=%s result_attribute = maildrop special_result_attribute = memberdn $ postmap -q [email protected] \ ldap:/etc/postfix/no_expand.cf ldap:/etc/postfix/expand.cf \ [email protected] $ postmap -q [email protected] \ ldap:/etc/postfix/no_expand.cf ldap:/etc/postfix/expand.cf \ [email protected],[email protected] $ postmap -q [email protected] \ ldap:/etc/postfix/no_expand.cf ldap:/etc/postfix/expand.cf \ [email protected]
非组对象和具有延迟扩展的组(即具有 maildrop 属性的组)会被重写为单个 maildrop 值。不具有 maildrop 属性的组则按照第二个用例进行扩展。这在 Postfix 2.4 及更高版本中允许更优雅的解决方案。
我们的最终用例与第三个相同,但这次使用了 Postfix 2.4 的新功能。现在我们可以使用单个 LDAP 表,无需假设转发组从未嵌套在展开的组内。
fancy.cf: ... search_base = dc=example, dc=com query_filter = mail=%s result_attribute = memberaddr special_result_attribute = memberdn terminal_result_attribute = maildrop leaf_result_attribute = mail $ postmap -q [email protected] ldap:/etc/postfix/fancy.cf \ [email protected] $ postmap -q [email protected] ldap:/etc/postfix/fancy.cf \ [email protected] $ postmap -q [email protected] ldap:/etc/postfix/fancy.cf \ [email protected],[email protected],[email protected],[email protected] $ postmap -q [email protected] ldap:/etc/postfix/fancy.cf \ [email protected]
上述配置通过 "terminal_result_attribute" 启用延迟展开,若该属性存在,则仅使用其结果,并抑制所有其他展开。否则,对于没有 "special_result_attribute"(非组对象)的叶对象,仅返回 "leaf_result_attribute",而 "result_attribute"(组的直接成员地址)在递归展开的每个级别都会返回,而不仅仅是叶节点。这个示例演示了 Postfix 2.4 组展开的所有功能。
LDAP 查找的其他用途
LDAP 查找的其他常见用途包括使用 Postfix 的规范化查找重写发件人和收件人,例如为了使从您的站点发送的邮件看起来像是来自 "[email protected]" 而不是 "[email protected]"。
注意事项和需要考虑的事项
- 本文档中使用的模式和属性名称仅为示例。它们没有特殊之处,只是其中一些是 LDAP 配置参数中的默认值。您可以使用任何喜欢的模式,并相应地配置 Postfix。
- 您可能需要确保 mailacceptinggeneralids 是唯一的,并且不是任何人都可以将自己的值设置为 postmaster 或 root 等。
一个条目可以包含任意数量的 mailacceptinggeneralids 或 maildrops。Maildrops 也可以是逗号分隔的地址列表。所有这些都会被查找并返回。例如,您可以定义一个用于邮件列表的条目,如下所示(警告!此示例仅用于说明):
dn: cn=Accounting Staff List, dc=example, dc=com cn: Accounting Staff List o: example.com objectclass: maillist mailacceptinggeneralid: accountingstaff mailacceptinggeneralid: accounting-staff maildrop: mylist-owner maildrop: an-accountant maildrop: some-other-accountant maildrop: this, that, theother
如果您使用 LDAP 映射进行除别名外的查找,可能需要确保查找结果合理。在虚拟查找的情况下,除邮件地址外的邮件投递点几乎毫无用处,因为 Postfix 无法确定如何为程序或文件交付设置所有权。您的 query_filter 可能需要类似以下格式:
query_filter = (&(mailacceptinggeneralid=%s)(!(|(maildrop="*|*")(maildrop="*:*")(maildrop="*/*"))))
此外,即使对于别名,您可能也不希望用户能够将他们的邮件投递设置为程序、包含文件等。这在"密封"服务器上尤为重要,因为这些服务器上不存在本地 UNIX 账户,用户仅存在于 LDAP 和 Cyrus 中。你可以只允许管理员账户拥有的目录条目使用这些"有趣"功能,这样如果对象的邮件投递为程序且不属于"cn=root"账户,就不会被识别为有效的本地用户。这需要您仔细考虑如何安全地实现,因为此类交付方式会带来一些影响。您可能决定不允许在 LDAP 查找中包含此类内容,而是通过 query_filter 禁止,并将类似 majordomo 列表保存在本地别名数据库中。
query_filter = (&(mailacceptinggeneralid=%s)(!(|(maildrop="*|*")(maildrop="*:*")(maildrop="*/*"))(owner=cn=root,dc=your,dc=com)))
- LDAP 查询比本地数据库或 DBM 查询慢。对于大多数站点,这不会成为瓶颈,但了解如何优化目录服务是个好主意。
- 多个 LDAP 映射如果仅在查询相关参数(如 base、scope、query_filter 等)上不同,则会共享相同的 LDAP 连接。为了利用这一点,应避免 LDAP 映射定义中的不必要的差异:主机选择顺序、版本、绑定、TLS 参数等应在多个映射中尽可能保持一致。
反馈
如有疑问,请发送至 [email protected]。请包含与 Postfix 配置相关的信息:postconf 中的 LDAP 相关输出、您使用的 LDAP 库以及所使用的目录服务器。如果问题涉及目录内容,请包含相关目录条目的部分内容。
致谢
- Manuel Guesdon:发现 timeout 属性中的一个 bug。
- John Hensley:支持多个 LDAP 源并提供更多可配置属性。
- Carsten Hoeger:搜索范围处理。
- LaMont Jones:域限制、URL 和 DN 搜索、多个结果属性。
- Mike Mattice:别名解析控制。
- Hery Rakotoarisoa:LDAPv3 更新补丁。
- 普拉巴特·K·辛格:编写了初始的 Postfix LDAP 查找和连接缓存。
- 基思·斯蒂文森:在查询中实现 RFC 2254 转义。
- Samuel Tardieu:发现搜索可以包含通配符,促使了对RFC 2254在查询中转义的工作。发现绑定中的一个错误。
- Sami Haahtinen:引用追踪和 v3 支持。
- Victor Duchovni:ldap_bind() 超时。基于 LaMont Jones 的修复:OpenLDAP 缓存废弃。递归、展开和搜索结果大小的限制。仅查询参数不同的 LDAP 连接共享。
- Liviu Daia:支持 SSL/STARTTLS。支持将映射定义存储在外部文件中(ldap:/path/ldap.cf),用于安全存储明文认证的密码。
- Liviu Daia 修订了配置界面并添加了 main.cf 配置功能。
- Liviu Daia 在 Jose Luis Tallon 和 Victor Duchovni 的进一步完善下,为 LDAP、MySQL 和 PostgreSQL 开发了通用查询、result_format、域和 expansion_limit 接口。
- Gunnar Wrobel 提供了限制 LDAP 搜索结果仅显示叶节点的功能的首次实现。Victor 将此功能泛化为 Postfix 2.4 的 "leaf_result_attribute" 功能。
- Quanah Gibson-Mount 贡献了对高级 LDAP SASL 机制的支持,超越了基于密码的 LDAP "简单" 绑定。
当然还有 Wietse。