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