1s!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN" "https://
2www.w3.org/TR/html4/loose.dtd">
3
4PPoossttffiixx LLDDAAPP HHoowwttoo
5
6-------------------------------------------------------------------------------
7
8LLDDAAPP SSuuppppoorrtt iinn PPoossttffiixx
9
10Postfix can use an LDAP directory as a source for any of its lookups: aliases
11(5), virtual(5), canonical(5), etc. This allows you to keep information for
12your mail service in a replicated network database with fine-grained access
13controls. By not storing it locally on the mail server, the administrators can
14maintain it from anywhere, and the users can control whatever bits of it you
15think appropriate. You can have multiple mail servers using the same
16information, without the hassle and delay of having to copy it to each.
17
18Topics covered in this document:
19
20 * Building Postfix with LDAP support
21 * Configuring LDAP lookups
22 * Example: aliases
23 * Example: virtual domains/addresses
24 * Example: expanding LDAP groups
25 * Other uses of LDAP lookups
26 * Notes and things to think about
27 * Feedback
28 * Credits
29
30BBuuiillddiinngg PPoossttffiixx wwiitthh LLDDAAPP ssuuppppoorrtt
31
32These instructions assume that you build Postfix from source code as described
33in the INSTALL document. Some modification may be required if you build Postfix
34from a vendor-specific source package.
35
36Note 1: Postfix no longer supports the LDAP version 1 interface.
37
38Note 2: to use LDAP with Debian GNU/Linux's Postfix, all you need is to install
39the postfix-ldap package and you're done. There is no need to recompile
40Postfix.
41
42You need to have LDAP libraries and include files installed somewhere on your
43system, and you need to configure the Postfix Makefiles accordingly.
44
45For example, to build the OpenLDAP libraries for use with Postfix (i.e. LDAP
46client code only), you could use the following command:
47
48 % ./configure --without-kerberos --without-cyrus-sasl --without-tls \
49 --without-threads --disable-slapd --disable-slurpd \
50 --disable-debug --disable-shared
51
52If you're using the libraries from OpenLDAP (https://www.openldap.org),
53something like this in the top level of your Postfix source tree should work:
54
55 % make tidy
56 % make makefiles CCARGS="-I/usr/local/include -DHAS_LDAP" \
57 AUXLIBS_LDAP="-L/usr/local/lib -lldap -L/usr/local/lib -llber"
58
59If your LDAP shared library is in a directory that the RUN-TIME linker does not
60know about, add a "-Wl,-R,/path/to/directory" option after "-lldap".
61
62Postfix versions before 3.0 use AUXLIBS instead of AUXLIBS_LDAP. With Postfix
633.0 and later, the old AUXLIBS variable still supports building a statically-
64loaded LDAP database client, but only the new AUXLIBS_LDAP variable supports
65building a dynamically-loaded or statically-loaded LDAP database client.
66
67 Failure to use the AUXLIBS_LDAP variable will defeat the purpose of dynamic
68 database client loading. Every Postfix executable file will have LDAP
69 database library dependencies. And that was exactly what dynamic database
70 client loading was meant to avoid.
71
72On Solaris 2.x you may have to specify run-time link information, otherwise
73ld.so will not find some of the shared libraries:
74
75 % make tidy
76 % make makefiles CCARGS="-I/usr/local/include -DHAS_LDAP" \
77 AUXLIBS_LDAP="-L/usr/local/lib -R/usr/local/lib -lldap \
78 -L/usr/local/lib -R/usr/local/lib -llber"
79
80The 'make tidy' command is needed only if you have previously built Postfix
81without LDAP support.
82
83Instead of '/usr/local' specify the actual locations of your LDAP include files
84and libraries. Be sure to not mix LDAP include files and LDAP libraries of
85different versions!!
86
87If your LDAP libraries were built with Kerberos support, you'll also need to
88include your Kerberos libraries in this line. Note that the KTH Kerberos IV
89libraries might conflict with Postfix's lib/libdns.a, which defines dns_lookup.
90If that happens, you'll probably want to link with LDAP libraries that lack
91Kerberos support just to build Postfix, as it doesn't support Kerberos binds to
92the LDAP server anyway. Sorry about the bother.
93
94If you're using one of the Netscape LDAP SDKs, you'll need to change the
95AUXLIBS line to point to libldap10.so or libldapssl30.so or whatever you have,
96and you may need to use the appropriate linker option (e.g. '-R') so the
97executables can find it at runtime.
98
99If you are using OpenLDAP, and the libraries were built with SASL support, you
100can add -DUSE_LDAP_SASL to the CCARGS to enable SASL support. For example:
101
102 CCARGS="-I/usr/local/include -DHAS_LDAP -DUSE_LDAP_SASL"
103
104CCoonnffiigguurriinngg LLDDAAPP llooookkuuppss
105
106In order to use LDAP lookups, define an LDAP source as a table lookup in
107main.cf, for example:
108
109 alias_maps = hash:/etc/aliases, ldap:/etc/postfix/ldap-aliases.cf
110
111The file /etc/postfix/ldap-aliases.cf can specify a great number of parameters,
112including parameters that enable LDAP SSL or STARTTLS, and LDAP SASL. For a
113complete description, see the ldap_table(5) manual page.
114
115EExxaammppllee:: llooccaall((88)) aalliiaasseess
116
117Here's a basic example for using LDAP to look up local(8) aliases. Assume that
118in main.cf, you have:
119
120 alias_maps = hash:/etc/aliases, ldap:/etc/postfix/ldap-aliases.cf
121
122and in ldap:/etc/postfix/ldap-aliases.cf you have:
123
124 server_host = ldap.example.com
125 search_base = dc=example, dc=com
126
127Upon receiving mail for a local address "ldapuser" that isn't found in the /
128etc/aliases database, Postfix will search the LDAP server listening at port 389
129on ldap.example.com. It will bind anonymously, search for any directory entries
130whose mailacceptinggeneralid attribute is "ldapuser", read the "maildrop"
131attributes of those found, and build a list of their maildrops, which will be
132treated as RFC822 addresses to which the message will be delivered.
133
134EExxaammppllee:: vviirrttuuaall ddoommaaiinnss//aaddddrreesssseess
135
136If you want to keep information for virtual lookups in your directory, it's
137only a little more complicated. First, you need to make sure Postfix knows
138about the virtual domain. An easy way to do that is to add the domain to the
139mailacceptinggeneralid attribute of some entry in the directory. Next, you'll
140want to make sure all of your virtual recipient's mailacceptinggeneralid
141attributes are fully qualified with their virtual domains. Finally, if you want
142to designate a directory entry as the default user for a virtual domain, just
143give it an additional mailacceptinggeneralid (or the equivalent in your
144directory) of "@fake.dom". That's right, no user part. If you don't want a
145catchall user, omit this step and mail to unknown users in the domain will
146simply bounce.
147
148In summary, you might have a catchall user for a virtual domain that looks like
149this:
150
151 dn: cn=defaultrecipient, dc=fake, dc=dom
152 objectclass: top
153 objectclass: virtualaccount
154 cn: defaultrecipient
155 owner: uid=root, dc=someserver, dc=isp, dc=dom
156 1 -> mailacceptinggeneralid: fake.dom
157 2 -> mailacceptinggeneralid: @fake.dom
158 3 -> maildrop: realuser@real.dom
159
160 1: Postfix knows fake.dom is a valid virtual domain when it looks for this
161 and gets something (the maildrop) back.
162
163 2: This causes any mail for unknown users in fake.dom to go to this entry
164 ...
165
166 3: ... and then to its maildrop.
167
168Normal users might simply have one mailacceptinggeneralid and maildrop, e.g.
169"normaluser@fake.dom" and "normaluser@real.dom".
170
171EExxaammppllee:: eexxppaannddiinngg LLDDAAPP ggrroouuppss
172
173LDAP is frequently used to store group member information. There are a number
174of ways of handling LDAP groups. We will show a few examples in order of
175increasing complexity, but owing to the number of independent variables, we can
176only present a tiny portion of the solution space. We show how to:
177
178 1. query groups as lists of addresses;
179
180 2. query groups as lists of user objects containing addresses;
181
182 3. forward special lists unexpanded to a separate list server, for moderation
183 or other processing;
184
185 4. handle complex schemas by controlling expansion and by treating leaf nodes
186 specially, using features that are new in Postfix 2.4.
187
188The example LDAP entries and implied schema below show two group entries
189("agroup" and "bgroup") and four user entries ("auser", "buser", "cuser" and
190"duser"). The group "agroup" has the users "auser" (1) and "buser" (2) as
191members via DN references in the multi-valued attribute "memberdn", and direct
192email addresses of two external users "auser@example.org" (3) and
193"buser@example.org" (4) stored in the multi-valued attribute "memberaddr". The
194same is true of "bgroup" and "cuser"/"duser" (6)/(7)/(8)/(9), but "bgroup" also
195has a "maildrop" attribute of "bgroup@mlm.example.com" (5):
196
197 dn: cn=agroup, dc=example, dc=com
198 objectclass: top
199 objectclass: ldapgroup
200 cn: agroup
201 mail: agroup@example.com
202 1 -> memberdn: uid=auser, dc=example, dc=com
203 2 -> memberdn: uid=buser, dc=example, dc=com
204 3 -> memberaddr: auser@example.org
205 4 -> memberaddr: buser@example.org
206
207 dn: cn=bgroup, dc=example, dc=com
208 objectclass: top
209 objectclass: ldapgroup
210 cn: bgroup
211 mail: bgroup@example.com
212 5 -> maildrop: bgroup@mlm.example.com
213 6 -> memberdn: uid=cuser, dc=example, dc=com
214 7 -> memberdn: uid=duser, dc=example, dc=com
215 8 -> memberaddr: cuser@example.org
216 9 -> memberaddr: duser@example.org
217
218 dn: uid=auser, dc=example, dc=com
219 objectclass: top
220 objectclass: ldapuser
221 uid: auser
222 10 -> mail: auser@example.com
223 11 -> maildrop: auser@mailhub.example.com
224
225 dn: uid=buser, dc=example, dc=com
226 objectclass: top
227 objectclass: ldapuser
228 uid: buser
229 12 -> mail: buser@example.com
230 13 -> maildrop: buser@mailhub.example.com
231
232 dn: uid=cuser, dc=example, dc=com
233 objectclass: top
234 objectclass: ldapuser
235 uid: cuser
236 14 -> mail: cuser@example.com
237
238 dn: uid=duser, dc=example, dc=com
239 objectclass: top
240 objectclass: ldapuser
241 uid: duser
242 15 -> mail: duser@example.com
243
244Our first use case ignores the "memberdn" attributes, and assumes that groups
245hold only direct "memberaddr" strings as in (3), (4), (8) and (9). The goal is
246to map the group address to the list of constituent "memberaddr" values. This
247is simple, ignoring the various connection related settings (hosts, ports, bind
248settings, timeouts, ...) we have:
249
250 simple.cf:
251 ...
252 search_base = dc=example, dc=com
253 query_filter = mail=%s
254 result_attribute = memberaddr
255 $ postmap -q agroup@example.com ldap:/etc/postfix/simple.cf \
256 auser@example.org,buser@example.org
257
258We search "dc=example, dc=com". The "mail" attribute is used in the
259query_filter to locate the right group, the "result_attribute" setting
260described in ldap_table(5) is used to specify that "memberaddr" values from the
261matching group are to be returned as a comma separated list. Always check
262tables using postmap(1) with the "-q" option, before deploying them into
263production use in main.cf.
264
265Our second use case instead expands "memberdn" attributes (1), (2), (6) and
266(7), follows the DN references and returns the "maildrop" of the referenced
267user entries. Here we use the "special_result_attribute" setting from
268ldap_table(5) to designate the "memberdn" attribute as holding DNs of the
269desired member entries. The "result_attribute" setting selects which attributes
270are returned from the selected DNs. It is important to choose a result
271attribute that is not also present in the group object, because result
272attributes are collected from both the group and the member DNs. In this case
273we choose "maildrop" and assume for the moment that groups never have a
274"maildrop" (the "bgroup" "maildrop" attribute is for a different use case). The
275returned data for "auser" and "buser" is from items (11) and (13) in the
276example data.
277
278 special.cf:
279 ...
280 search_base = dc=example, dc=com
281 query_filter = mail=%s
282 result_attribute = maildrop
283 special_result_attribute = memberdn
284 $ postmap -q agroup@example.com ldap:/etc/postfix/special.cf \
285 auser@mailhub.example.com,buser@mailhub.example.com
286
287Note: if the desired member object result attribute is always also present in
288the group, you get surprising results: the expansion also returns the address
289of the group. This is a known limitation of Postfix releases prior to 2.4, and
290is addressed in the new with Postfix 2.4 "leaf_result_attribute" feature
291described in ldap_table(5).
292
293Our third use case has some groups that are expanded immediately, and other
294groups that are forwarded to a dedicated mailing list manager host for delayed
295expansion. This uses two LDAP tables, one for users and forwarded groups and a
296second for groups that can be expanded immediately. It is assumed that groups
297that require forwarding are never nested members of groups that are directly
298expanded.
299
300 no_expand.cf:
301 ...
302 search_base = dc=example, dc=com
303 query_filter = mail=%s
304 result_attribute = maildrop
305 expand.cf
306 ...
307 search_base = dc=example, dc=com
308 query_filter = mail=%s
309 result_attribute = maildrop
310 special_result_attribute = memberdn
311 $ postmap -q auser@example.com \
312 ldap:/etc/postfix/no_expand.cf ldap:/etc/postfix/expand.cf \
313 auser@mailhub.example.com
314 $ postmap -q agroup@example.com \
315 ldap:/etc/postfix/no_expand.cf ldap:/etc/postfix/expand.cf \
316 auser@mailhub.example.com,buser@mailhub.example.com
317 $ postmap -q bgroup@example.com \
318 ldap:/etc/postfix/no_expand.cf ldap:/etc/postfix/expand.cf \
319 bgroup@mlm.example.com
320
321Non-group objects and groups with delayed expansion (those that have a maildrop
322attribute) are rewritten to a single maildrop value. Groups that don't have a
323maildrop are expanded as the second use case. This admits a more elegant
324solution with Postfix 2.4 and later.
325
326Our final use case is the same as the third, but this time uses new features in
327Postfix 2.4. We now are able to use just one LDAP table and no longer need to
328assume that forwarded groups are never nested inside expanded groups.
329
330 fancy.cf:
331 ...
332 search_base = dc=example, dc=com
333 query_filter = mail=%s
334 result_attribute = memberaddr
335 special_result_attribute = memberdn
336 terminal_result_attribute = maildrop
337 leaf_result_attribute = mail
338 $ postmap -q auser@example.com ldap:/etc/postfix/fancy.cf \
339 auser@mailhub.example.com
340 $ postmap -q cuser@example.com ldap:/etc/postfix/fancy.cf \
341 cuser@example.com
342 $ postmap -q agroup@example.com ldap:/etc/postfix/fancy.cf \
343
344 auser@mailhub.example.com,buser@mailhub.example.com,auser@example.org,buser@example.org
345 $ postmap -q bgroup@example.com ldap:/etc/postfix/fancy.cf \
346 bgroup@mlm.example.com
347
348Above, delayed expansion is enabled via "terminal_result_attribute", which, if
349present, is used as the sole result and all other expansion is suppressed.
350Otherwise, the "leaf_result_attribute" is only returned for leaf objects that
351don't have a "special_result_attribute" (non-groups), while the
352"result_attribute" (direct member address of groups) is returned at every level
353of recursive expansion, not just the leaf nodes. This fancy example illustrates
354all the features of Postfix 2.4 group expansion.
355
356OOtthheerr uusseess ooff LLDDAAPP llooookkuuppss
357
358Other common uses for LDAP lookups include rewriting senders and recipients
359with Postfix's canonical lookups, for example in order to make mail leaving
360your site appear to be coming from "First.Last@example.com" instead of
361"userid@example.com".
362
363NNootteess aanndd tthhiinnggss ttoo tthhiinnkk aabboouutt
364
365 * The bits of schema and attribute names used in this document are just
366 examples. There's nothing special about them, other than that some are the
367 defaults in the LDAP configuration parameters. You can use whatever schema
368 you like, and configure Postfix accordingly.
369
370 * You probably want to make sure that mailacceptinggeneralids are unique, and
371 that not just anyone can specify theirs as postmaster or root, say.
372
373 * An entry can have an arbitrary number of mailacceptinggeneralids or
374 maildrops. Maildrops can also be comma-separated lists of addresses. They
375 will all be found and returned by the lookups. For example, you could
376 define an entry intended for use as a mailing list that looks like this
377 (Warning! Schema made up just for this example):
378
379 dn: cn=Accounting Staff List, dc=example, dc=com
380 cn: Accounting Staff List
381 o: example.com
382 objectclass: maillist
383 mailacceptinggeneralid: accountingstaff
384 mailacceptinggeneralid: accounting-staff
385 maildrop: mylist-owner
386 maildrop: an-accountant
387 maildrop: some-other-accountant
388 maildrop: this, that, theother
389
390 * If you use an LDAP map for lookups other than aliases, you may have to make
391 sure the lookup makes sense. In the case of virtual lookups, maildrops
392 other than mail addresses are pretty useless, because Postfix can't know
393 how to set the ownership for program or file delivery. Your qquueerryy__ffiilltteerr
394 should probably look something like this:
395
396 query_filter = (&(mailacceptinggeneralid=%s)(!(|(maildrop="*|*")
397 (maildrop="*:*")(maildrop="*/*"))))
398
399 * And for that matter, even for aliases, you may not want users to be able to
400 specify their maildrops as programs, includes, etc. This might be
401 particularly pertinent on a "sealed" server where they don't have local
402 UNIX accounts, but exist only in LDAP and Cyrus. You might allow the fun
403 stuff only for directory entries owned by an administrative account, so
404 that if the object had a program as its maildrop and weren't owned by
405 "cn=root" it wouldn't be returned as a valid local user. This will require
406 some thought on your part to implement safely, considering the
407 ramifications of this type of delivery. You may decide it's not worth the
408 bother to allow any of that nonsense in LDAP lookups, ban it in the
409 qquueerryy__ffiilltteerr, and keep things like majordomo lists in local alias
410 databases.
411
412 query_filter = (&(mailacceptinggeneralid=%s)(!(|(maildrop="*|*")
413 (maildrop="*:*")(maildrop="*/*"))(owner=cn=root, dc=your, dc=com)))
414
415 * LDAP lookups are slower than local DB or DBM lookups. For most sites they
416 won't be a bottleneck, but it's a good idea to know how to tune your
417 directory service.
418
419 * Multiple LDAP maps share the same LDAP connection if they differ only in
420 their query related parameters: base, scope, query_filter, and so on. To
421 take advantage of this, avoid spurious differences in the definitions of
422 LDAP maps: host selection order, version, bind, tls parameters, ... should
423 be the same for multiple maps whenever possible.
424
425FFeeeeddbbaacckk
426
427If you have questions, send them to postfix-users@postfix.org. Please include
428relevant information about your Postfix setup: LDAP-related output from
429postconf, which LDAP libraries you built with, and which directory server
430you're using. If your question involves your directory contents, please include
431the applicable bits of some directory entries.
432
433CCrreeddiittss
434
435 * Manuel Guesdon: Spotted a bug with the timeout attribute.
436 * John Hensley: Multiple LDAP sources with more configurable attributes.
437 * Carsten Hoeger: Search scope handling.
438 * LaMont Jones: Domain restriction, URL and DN searches, multiple result
439 attributes.
440 * Mike Mattice: Alias dereferencing control.
441 * Hery Rakotoarisoa: Patches for LDAPv3 updating.
442 * Prabhat K Singh: Wrote the initial Postfix LDAP lookups and connection
443 caching.
444 * Keith Stevenson: RFC 2254 escaping in queries.
445 * Samuel Tardieu: Noticed that searches could include wildcards, prompting
446 the work on RFC 2254 escaping in queries. Spotted a bug in binding.
447 * Sami Haahtinen: Referral chasing and v3 support.
448 * Victor Duchovni: ldap_bind() timeout. With fixes from LaMont Jones:
449 OpenLDAP cache deprecation. Limits on recursion, expansion and search
450 results size. LDAP connection sharing for maps differing only in the query
451 parameters.
452 * Liviu Daia: Support for SSL/STARTTLS. Support for storing map definitions
453 in external files (ldap:/path/ldap.cf) needed to securely store passwords
454 for plain auth.
455 * Liviu Daia revised the configuration interface and added the main.cf
456 configuration feature.
457 * Liviu Daia with further refinements from Jose Luis Tallon and Victor
458 Duchovni developed the common query, result_format, domain and
459 expansion_limit interface for LDAP, MySQL and PosgreSQL.
460 * Gunnar Wrobel provided a first implementation of a feature to limit LDAP
461 search results to leaf nodes only. Victor generalized this into the Postfix
462 2.4 "leaf_result_attribute" feature.
463 * Quanah Gibson-Mount contributed support for advanced LDAP SASL mechanisms,
464 beyond the password-based LDAP "simple" bind.
465
466And of course Wietse.
467
468