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