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 -> mailacceptinggeneralid: fake.dom 2012 -> mailacceptinggeneralid: @fake.dom 2023 -> 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 -> memberdn: uid=auser, dc=example, dc=com 2652 -> memberdn: uid=buser, dc=example, dc=com 2663 -> memberaddr: auser@example.org 2674 -> 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 -> maildrop: bgroup@mlm.example.com 2786 -> memberdn: uid=cuser, dc=example, dc=com 2797 -> memberdn: uid=duser, dc=example, dc=com 2808 -> memberaddr: cuser@example.org 2819 -> 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 -> mail: auser@example.com 29111 -> 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 -> mail: buser@example.com 30113 -> 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 -> 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 -> 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 = (&(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 = (&(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