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