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=utf-8"> 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_LDAP="-L/usr/local/lib -lldap -L/usr/local/lib -llber" 96</pre> 97</blockquote> 98 99<p> If your LDAP shared library is in a directory that the RUN-TIME 100linker does not know about, add a "-Wl,-R,/path/to/directory" option after 101"-lldap". </p> 102 103<p> Postfix versions before 3.0 use AUXLIBS instead of AUXLIBS_LDAP. 104With Postfix 3.0 and later, the old AUXLIBS variable still supports 105building a statically-loaded LDAP database client, but only the new 106AUXLIBS_LDAP variable supports building a dynamically-loaded or 107statically-loaded LDAP database client. </p> 108 109<blockquote> 110 111<p> Failure to use the AUXLIBS_LDAP variable will defeat the purpose 112of dynamic database client loading. Every Postfix executable file 113will have LDAP database library dependencies. And that was exactly 114what dynamic database client loading was meant to avoid. </p> 115 116</blockquote> 117 118<p> On Solaris 2.x you may have to specify run-time link information, 119otherwise ld.so will not find some of the shared libraries: </p> 120 121<blockquote> 122<pre> 123% make tidy 124% make makefiles CCARGS="-I/usr/local/include -DHAS_LDAP" \ 125 AUXLIBS_LDAP="-L/usr/local/lib -R/usr/local/lib -lldap \ 126 -L/usr/local/lib -R/usr/local/lib -llber" 127</pre> 128</blockquote> 129 130<p> The 'make tidy' command is needed only if you have previously 131built Postfix without LDAP support. </p> 132 133<p> Instead of '/usr/local' specify the actual locations of your 134LDAP include files and libraries. Be sure to not mix LDAP include 135files and LDAP libraries of different versions!! </p> 136 137<p> If your LDAP libraries were built with Kerberos support, you'll 138also need to include your Kerberos libraries in this line. Note 139that the KTH Kerberos IV libraries might conflict with Postfix's 140lib/libdns.a, which defines dns_lookup. If that happens, you'll 141probably want to link with LDAP libraries that lack Kerberos support 142just to build Postfix, as it doesn't support Kerberos binds to the 143LDAP server anyway. Sorry about the bother. </p> 144 145<p> If you're using one of the Netscape LDAP SDKs, you'll need to 146change the AUXLIBS line to point to libldap10.so or libldapssl30.so 147or whatever you have, and you may need to use the appropriate linker 148option (e.g. '-R') so the executables can find it at runtime. </p> 149 150<p> If you are using OpenLDAP, and the libraries were built with SASL 151support, you can add -DUSE_LDAP_SASL to the CCARGS to enable SASL support. 152For example: </p> 153 154<blockquote> 155<pre> 156 CCARGS="-I/usr/local/include -DHAS_LDAP -DUSE_LDAP_SASL" 157</pre> 158</blockquote> 159 160<h2><a name="config">Configuring LDAP lookups</a></h2> 161 162<p> In order to use LDAP lookups, define an LDAP source 163as a table lookup in main.cf, for example: </p> 164 165<blockquote> 166<pre> 167alias_maps = hash:/etc/aliases, ldap:/etc/postfix/ldap-aliases.cf 168</pre> 169</blockquote> 170 171<p> The file /etc/postfix/ldap-aliases.cf can specify a great number 172of parameters, including parameters that enable LDAP SSL or STARTTLS, 173and LDAP SASL. For a complete description, see the ldap_table(5) 174manual page. </p> 175 176<h2><a name="example_alias">Example: local(8) aliases</a></h2> 177 178<p> Here's a basic example for using LDAP to look up local(8) 179aliases. Assume that in main.cf, you have: </p> 180 181<blockquote> 182<pre> 183alias_maps = hash:/etc/aliases, ldap:/etc/postfix/ldap-aliases.cf 184</pre> 185</blockquote> 186 187<p> and in ldap:/etc/postfix/ldap-aliases.cf you have: </p> 188 189<blockquote> 190<pre> 191server_host = ldap.example.com 192search_base = dc=example, dc=com 193</pre> 194</blockquote> 195 196<p> Upon receiving mail for a local address "ldapuser" that isn't 197found in the /etc/aliases database, Postfix will search the LDAP 198server listening at port 389 on ldap.example.com. It will bind anonymously, 199search for any directory entries whose mailacceptinggeneralid 200attribute is "ldapuser", read the "maildrop" attributes of those 201found, and build a list of their maildrops, which will be treated 202as RFC822 addresses to which the message will be delivered. </p> 203 204<h2><a name="example_virtual">Example: virtual domains/addresses</a></h2> 205 206<p> If you want to keep information for virtual lookups in your 207directory, it's only a little more complicated. First, you need to 208make sure Postfix knows about the virtual domain. An easy way to 209do that is to add the domain to the mailacceptinggeneralid attribute 210of some entry in the directory. Next, you'll want to make sure all 211of your virtual recipient's mailacceptinggeneralid attributes are 212fully qualified with their virtual domains. Finally, if you want 213to designate a directory entry as the default user for a virtual 214domain, just give it an additional mailacceptinggeneralid (or the 215equivalent in your directory) of "@fake.dom". That's right, no 216user part. If you don't want a catchall user, omit this step and 217mail to unknown users in the domain will simply bounce. </p> 218 219<p> In summary, you might have a catchall user for a virtual domain 220that looks like this: </p> 221 222<blockquote> 223<pre> 224 dn: cn=defaultrecipient, dc=fake, dc=dom 225 objectclass: top 226 objectclass: virtualaccount 227 cn: defaultrecipient 228 owner: uid=root, dc=someserver, dc=isp, dc=dom 2291 -> mailacceptinggeneralid: fake.dom 2302 -> mailacceptinggeneralid: @fake.dom 2313 -> maildrop: realuser@real.dom 232</pre> 233</blockquote> 234 235<dl compact> 236 237<dd> <p> 1: Postfix knows fake.dom is a valid virtual domain when 238it looks for this and gets something (the maildrop) back. </p> 239 240<dd> <p> 2: This causes any mail for unknown users in fake.dom to 241go to this entry ... </p> 242 243<dd> <p> 3: ... and then to its maildrop. </p> 244 245</dl> 246 247<p> Normal users might simply have one mailacceptinggeneralid and 248maildrop, e.g. "normaluser@fake.dom" and "normaluser@real.dom". 249</p> 250 251<h2><a name="example_group">Example: expanding LDAP groups</a></h2> 252 253<p> 254LDAP is frequently used to store group member information. There are a 255number of ways of handling LDAP groups. We will show a few examples in 256order of increasing complexity, but owing to the number of independent 257variables, we can only present a tiny portion of the solution space. 258We show how to: 259</p> 260 261<ol> 262 263<li> <p> query groups as lists of addresses; </p> 264 265<li> <p> query groups as lists of user objects containing addresses; </p> 266 267<li> <p> forward special lists unexpanded to a separate list server, 268for moderation or other processing; </p> 269 270<li> <p> handle complex schemas by controlling expansion and by treating 271leaf nodes specially, using features that are new in Postfix 2.4. </p> 272 273</ol> 274 275<p> 276The example LDAP entries and implied schema below show two group entries 277("agroup" and "bgroup") and four user entries ("auser", "buser", "cuser" 278and "duser"). The group "agroup" has the users "auser" (1) and "buser" (2) 279as members via DN references in the multi-valued attribute "memberdn", and 280direct email addresses of two external users "auser@example.org" (3) and 281"buser@example.org" (4) stored in the multi-valued attribute "memberaddr". 282The same is true of "bgroup" and "cuser"/"duser" (6)/(7)/(8)/(9), but 283"bgroup" also has a "maildrop" attribute of "bgroup@mlm.example.com" 284(5): </p> 285 286<blockquote> 287<pre> 288 dn: cn=agroup, dc=example, dc=com 289 objectclass: top 290 objectclass: ldapgroup 291 cn: agroup 292 mail: agroup@example.com 2931 -> memberdn: uid=auser, dc=example, dc=com 2942 -> memberdn: uid=buser, dc=example, dc=com 2953 -> memberaddr: auser@example.org 2964 -> memberaddr: buser@example.org 297</pre> 298<br> 299 300<pre> 301 dn: cn=bgroup, dc=example, dc=com 302 objectclass: top 303 objectclass: ldapgroup 304 cn: bgroup 305 mail: bgroup@example.com 3065 -> maildrop: bgroup@mlm.example.com 3076 -> memberdn: uid=cuser, dc=example, dc=com 3087 -> memberdn: uid=duser, dc=example, dc=com 3098 -> memberaddr: cuser@example.org 3109 -> memberaddr: duser@example.org 311</pre> 312<br> 313 314<pre> 315 dn: uid=auser, dc=example, dc=com 316 objectclass: top 317 objectclass: ldapuser 318 uid: auser 31910 -> mail: auser@example.com 32011 -> maildrop: auser@mailhub.example.com 321</pre> 322<br> 323 324<pre> 325 dn: uid=buser, dc=example, dc=com 326 objectclass: top 327 objectclass: ldapuser 328 uid: buser 32912 -> mail: buser@example.com 33013 -> maildrop: buser@mailhub.example.com 331</pre> 332<br> 333 334<pre> 335 dn: uid=cuser, dc=example, dc=com 336 objectclass: top 337 objectclass: ldapuser 338 uid: cuser 33914 -> mail: cuser@example.com 340</pre> 341<br> 342 343<pre> 344 dn: uid=duser, dc=example, dc=com 345 objectclass: top 346 objectclass: ldapuser 347 uid: duser 34815 -> mail: duser@example.com 349</pre> 350<br> 351 352</blockquote> 353 354<p> Our first use case ignores the "memberdn" attributes, and assumes 355that groups hold only direct "memberaddr" strings as in (3), (4), (8) and 356(9). The goal is to map the group address to the list of constituent 357"memberaddr" values. This is simple, ignoring the various connection 358related settings (hosts, ports, bind settings, timeouts, ...) we have: 359</p> 360 361<blockquote> 362<pre> 363 simple.cf: 364 ... 365 search_base = dc=example, dc=com 366 query_filter = mail=%s 367 result_attribute = memberaddr 368 $ postmap -q agroup@example.com ldap:/etc/postfix/simple.cf \ 369 auser@example.org,buser@example.org 370</pre> 371</blockquote> 372 373<p> We search "dc=example, dc=com". The "mail" attribute is used in the 374query_filter to locate the right group, the "result_attribute" setting 375described in ldap_table(5) is used to specify that "memberaddr" values 376from the matching group are to be returned as a comma separated list. 377Always check tables using postmap(1) with the "-q" option, before 378deploying them into production use in main.cf. </p> 379 380<p> Our second use case instead expands "memberdn" attributes (1), (2), 381(6) and (7), follows the DN references and returns the "maildrop" of the 382referenced user entries. Here we use the "special_result_attribute" 383setting from ldap_table(5) to designate the "memberdn" attribute 384as holding DNs of the desired member entries. The "result_attribute" 385setting selects which attributes are returned from the selected DNs. It 386is important to choose a result attribute that is not also present in 387the group object, because result attributes are collected from both 388the group and the member DNs. In this case we choose "maildrop" and 389assume for the moment that groups never have a "maildrop" (the "bgroup" 390"maildrop" attribute is for a different use case). The returned data for 391"auser" and "buser" is from items (11) and (13) in the example data. </p> 392 393<blockquote> 394<pre> 395 special.cf: 396 ... 397 search_base = dc=example, dc=com 398 query_filter = mail=%s 399 result_attribute = maildrop 400 special_result_attribute = memberdn 401 $ postmap -q agroup@example.com ldap:/etc/postfix/special.cf \ 402 auser@mailhub.example.com,buser@mailhub.example.com 403</pre> 404</blockquote> 405 406<p> Note: if the desired member object result attribute is always also 407present in the group, you get surprising results: the expansion also 408returns the address of the group. This is a known limitation of Postfix 409releases prior to 2.4, and is addressed in the new with Postfix 2.4 410"leaf_result_attribute" feature described in ldap_table(5). </p> 411 412<p> Our third use case has some groups that are expanded immediately, 413and other groups that are forwarded to a dedicated mailing list manager 414host for delayed expansion. This uses two LDAP tables, one for users 415and forwarded groups and a second for groups that can be expanded 416immediately. It is assumed that groups that require forwarding are 417never nested members of groups that are directly expanded. </p> 418 419<blockquote> 420<pre> 421 no_expand.cf: 422 ... 423 search_base = dc=example, dc=com 424 query_filter = mail=%s 425 result_attribute = maildrop 426 expand.cf 427 ... 428 search_base = dc=example, dc=com 429 query_filter = mail=%s 430 result_attribute = maildrop 431 special_result_attribute = memberdn 432 $ postmap -q auser@example.com \ 433 ldap:/etc/postfix/no_expand.cf ldap:/etc/postfix/expand.cf \ 434 auser@mailhub.example.com 435 $ postmap -q agroup@example.com \ 436 ldap:/etc/postfix/no_expand.cf ldap:/etc/postfix/expand.cf \ 437 auser@mailhub.example.com,buser@mailhub.example.com 438 $ postmap -q bgroup@example.com \ 439 ldap:/etc/postfix/no_expand.cf ldap:/etc/postfix/expand.cf \ 440 bgroup@mlm.example.com 441</pre> 442</blockquote> 443 444<p> Non-group objects and groups with delayed expansion (those that have a 445maildrop attribute) are rewritten to a single maildrop value. Groups that 446don't have a maildrop are expanded as the second use case. This admits 447a more elegant solution with Postfix 2.4 and later. </p> 448 449<p> Our final use case is the same as the third, but this time uses new 450features in Postfix 2.4. We now are able to use just one LDAP table and 451no longer need to assume that forwarded groups are never nested inside 452expanded groups. </p> 453 454<blockquote> 455<pre> 456 fancy.cf: 457 ... 458 search_base = dc=example, dc=com 459 query_filter = mail=%s 460 result_attribute = memberaddr 461 special_result_attribute = memberdn 462 terminal_result_attribute = maildrop 463 leaf_result_attribute = mail 464 $ postmap -q auser@example.com ldap:/etc/postfix/fancy.cf \ 465 auser@mailhub.example.com 466 $ postmap -q cuser@example.com ldap:/etc/postfix/fancy.cf \ 467 cuser@example.com 468 $ postmap -q agroup@example.com ldap:/etc/postfix/fancy.cf \ 469 auser@mailhub.example.com,buser@mailhub.example.com,auser@example.org,buser@example.org 470 $ postmap -q bgroup@example.com ldap:/etc/postfix/fancy.cf \ 471 bgroup@mlm.example.com 472</pre> 473</blockquote> 474 475<p> Above, delayed expansion is enabled via "terminal_result_attribute", 476which, if present, is used as the sole result and all other expansion is 477suppressed. Otherwise, the "leaf_result_attribute" is only returned for 478leaf objects that don't have a "special_result_attribute" (non-groups), 479while the "result_attribute" (direct member address of groups) is returned 480at every level of recursive expansion, not just the leaf nodes. This fancy 481example illustrates all the features of Postfix 2.4 group expansion. </p> 482 483<h2><a name="other">Other uses of LDAP lookups</a></h2> 484 485Other common uses for LDAP lookups include rewriting senders and 486recipients with Postfix's canonical lookups, for example in order 487to make mail leaving your site appear to be coming from 488"First.Last@example.com" instead of "userid@example.com". 489 490<h2><a name="hmmmm">Notes and things to think about</a></h2> 491 492<ul> 493 494<li> <p> The bits of schema and attribute names used in this document are just 495 examples. There's nothing special about them, other than that some are 496 the defaults in the LDAP configuration parameters. You can use 497 whatever schema you like, and configure Postfix accordingly. </p> 498 499<li> <p> You probably want to make sure that mailacceptinggeneralids are 500 unique, and that not just anyone can specify theirs as postmaster or 501 root, say. </p> 502 503<li> <p> An entry can have an arbitrary number of mailacceptinggeneralids or 504 maildrops. Maildrops can also be comma-separated lists of addresses. 505 They will all be found and returned by the lookups. For example, you 506 could define an entry intended for use as a mailing list that looks 507 like this (Warning! Schema made up just for this example): </p> 508 509<blockquote> 510<pre> 511dn: cn=Accounting Staff List, dc=example, dc=com 512cn: Accounting Staff List 513o: example.com 514objectclass: maillist 515mailacceptinggeneralid: accountingstaff 516mailacceptinggeneralid: accounting-staff 517maildrop: mylist-owner 518maildrop: an-accountant 519maildrop: some-other-accountant 520maildrop: this, that, theother 521</pre> 522</blockquote> 523 524<li> <p> If you use an LDAP map for lookups other than aliases, you may have to 525 make sure the lookup makes sense. In the case of virtual lookups, 526 maildrops other than mail addresses are pretty useless, because 527 Postfix can't know how to set the ownership for program or file 528 delivery. Your <b>query_filter</b> should probably look something like this: </p> 529 530<blockquote> 531<pre> 532query_filter = (&(mailacceptinggeneralid=%s)(!(|(maildrop="*|*")(maildrop="*:*")(maildrop="*/*")))) 533</pre> 534</blockquote> 535 536<li> <p> And for that matter, even for aliases, you may not want users to be able to 537 specify their maildrops as programs, includes, etc. This might be 538 particularly pertinent on a "sealed" server where they don't have 539 local UNIX accounts, but exist only in LDAP and Cyrus. You might allow 540 the fun stuff only for directory entries owned by an administrative 541 account, 542 so that if the object had a program as its maildrop and weren't owned 543 by "cn=root" it wouldn't be returned as a valid local user. This will 544 require some thought on your part to implement safely, considering the 545 ramifications of this type of delivery. You may decide it's not worth 546 the bother to allow any of that nonsense in LDAP lookups, ban it in 547 the <b>query_filter</b>, and keep things like majordomo lists in local alias 548 databases. </p> 549 550<blockquote> 551<pre> 552query_filter = (&(mailacceptinggeneralid=%s)(!(|(maildrop="*|*")(maildrop="*:*")(maildrop="*/*"))(owner=cn=root, dc=your, dc=com))) 553</pre> 554</blockquote> 555 556<li> <p> LDAP lookups are slower than local DB or DBM lookups. For most sites 557 they won't be a bottleneck, but it's a good idea to know how to tune 558 your directory service. </p> 559 560<li> <p> Multiple LDAP maps share the same LDAP connection if they differ 561 only in their query related parameters: base, scope, query_filter, and 562 so on. To take advantage of this, avoid spurious differences in the 563 definitions of LDAP maps: host selection order, version, bind, tls 564 parameters, ... should be the same for multiple maps whenever possible. </p> 565 566</ul> 567 568<h2><a name="feedback">Feedback</a></h2> 569 570<p> If you have questions, send them to postfix-users@postfix.org. Please 571include relevant information about your Postfix setup: LDAP-related 572output from postconf, which LDAP libraries you built with, and which 573directory server you're using. If your question involves your directory 574contents, please include the applicable bits of some directory entries. </p> 575 576<h2><a name="credits">Credits</a></h2> 577 578<ul> 579 580<li>Manuel Guesdon: Spotted a bug with the timeout attribute. 581 582<li>John Hensley: Multiple LDAP sources with more configurable attributes. 583 584<li>Carsten Hoeger: Search scope handling. 585 586<li>LaMont Jones: Domain restriction, URL and DN searches, multiple result 587 attributes. 588 589<li>Mike Mattice: Alias dereferencing control. 590 591<li>Hery Rakotoarisoa: Patches for LDAPv3 updating. 592 593<li>Prabhat K Singh: Wrote the initial Postfix LDAP lookups and connection caching. 594 595<li>Keith Stevenson: RFC 2254 escaping in queries. 596 597<li>Samuel Tardieu: Noticed that searches could include wildcards, prompting 598 the work on RFC 2254 escaping in queries. Spotted a bug 599 in binding. 600 601<li>Sami Haahtinen: Referral chasing and v3 support. 602 603<li>Victor Duchovni: ldap_bind() timeout. With fixes from LaMont Jones: 604 OpenLDAP cache deprecation. Limits on recursion, expansion 605 and search results size. LDAP connection sharing for maps 606 differing only in the query parameters. 607 608<li>Liviu Daia: Support for SSL/STARTTLS. Support for storing map definitions in 609 external files (ldap:/path/ldap.cf) needed to securely store 610 passwords for plain auth. 611 612<li>Liviu Daia revised the configuration interface and added the main.cf 613 configuration feature.</li> 614 615<li>Liviu Daia with further refinements from Jose Luis Tallon and 616Victor Duchovni developed the common query, result_format, domain and 617expansion_limit interface for LDAP, MySQL and PosgreSQL.</li> 618 619<li>Gunnar Wrobel provided a first implementation of a feature to 620limit LDAP search results to leaf nodes only. Victor generalized 621this into the Postfix 2.4 "leaf_result_attribute" feature. </li> 622 623<li>Quanah Gibson-Mount contributed support for advanced LDAP SASL 624mechanisms, beyond the password-based LDAP "simple" bind. </li> 625 626</ul> 627 628And of course Wietse. 629 630</body> 631 632</html> 633