Using Exim with Zarafa

So 01 April 2012
By tdk

Zarafa normaly uses postfix as its MTA. Since I know Exim much better and regard it overall as the better MTA I want to replace postfix with Exim in my installations. This is my take on it.

First of all set several macros in your exim4.conf. This way your routers and querys will stay much more readable.

Use this for an Active Directory / OpenLDAP Setup:

LDAPSERVER = zcpadms01.skynet.private
ZARAFASERVER = zcpadms01.skynet.private
ldap_default_servers = LDAPSERVER
LDAPUSER = cn=Administrator,cn=Users,dc=samdom,dc=skynet,dc=private
LDAPPASS = geheim
LDAPSEARCHBASE = dc=samdom,dc=skynet,dc=private
LDAPCRED = user=LDAPUSER pass=LDAPPASS

If you don't have your users in a directory but use the DB plugin set this:

MYSQL_SERVER = localhost
MYSQL_DB = zarafa
MYSQL_USER = zcpread
MYSQL_PASS = readonly
# Query for checking if a Mailaddress belongs to a Zarafa User. Works with ZCP 7.x
ZARAFA_USER = SELECT DISTINCT value FROM objectproperty \
WHERE propname = 'emailaddress' \
AND objectid = (SELECT DISTINCT objectid FROM objectproperty \
WHERE value = '\${quote_mysql:\$local_part@\$domain}');

hide mysql_servers = MYSQL_SERVER/MYSQL_DB/MYSQL_USER/MYSQL_PASS

Of course you should authenticate with a user that can only read what is neccesary instead of your admin-user in both cases.

Now you need to create several routers to enable Exim to deliver mail to aliases, groups and users.

Delivery to aliases

LDAP Setup

The condition checks if there is an object that has the mailaddress in question in its zarafaAliases attribute and then gets the real address of that object from the mail attribute.

zarafa_aliases:
debug_print = "R: zarafa_aliases LDAP lookup for \$local_part@\$domain"
driver = redirect
domains = +local_domains
condition = \${lookup ldap {LDAPCRED ldap:///LDAPSEARCHBASE?mail?sub?(zarafaAliases=*\${quote_ldap:\$local_part@\$domain}*)}}
data = \${lookup ldap {LDAPCRED ldap:///LDAPSEARCHBASE?mail?sub?(zarafaAliases=*\${quote_ldap:\$local_part@\$domain}*)}}

Active Directory Setup

Basicly the same as with openLDAP, excpet that the attribute that holds the aliasaddresse is called otherMailbox in AD.

zarafa_aliases:
debug_print = "R: zarafa_aliases LDAP lookup for \$local_part@\$domain"
driver = redirect
domains = +local_domains
condition = \${lookup ldap {LDAPCRED ldap:///LDAPSEARCHBASE?mail?sub?(otherMailbox=*\${quote_ldap:\$local_part@\$domain}*)}}
data = \${lookup ldap {LDAPCRED ldap:///LDAPSEARCHBASE?mail?sub?(otherMailbox=*\${quote_ldap:\$local_part@\$domain}*)}}

DB Setup

If you are using the DB plugin your aliases and groupaddresses have to be done by the MTA. You could just do this in your /etc/aliases file but I prefer to
have this in a seperate file named /etc/zarafa/aliases+groups.

# User aliases
Bart.Simpson:   bsimpson
B.Simpson:      bsimpson
bs:             bsimpson

Lisa.Simpson:   lsimpson
L.Simpson:      lsimpson
ls:             lsimpson

# Groups
Simpsons:       bsimpson, lsimpson

The router is just a copy of the normal aliasrouter with another file to lookup the data in.

zarafa_aliases:
debug_print = "R: zarafa_aliases for \$local_part@\$domain"
driver = redirect
domains = +local_domains
allow_defer
allow_fail
data = \${lookup{\$local_part}lsearch{/etc/zarafa/aliases+groups}}
file_transport = address_file
pipe_transport = address_pipe
retry_use_local_part

Delivery to groups

Active Directory Setup

This one took me some time to figure out: In AD for a group the DN of each groupmember is in the attribute member.

member: CN=Montgomery Burns,OU=Benutzer,DC=samdom,DC=skynet,DC=private
member: CN=Waylan Smithers,OU=Benutzer,DC=samdom,DC=skynet,DC=private

So you would need a query to get all the member attributes of the group that has the mailaddress in question. That would get you a bunch of DNs to which you
need to get the mailaddresses in another query.  Here Exims lookup types ldapm (more than one result to a query) and ldapdn (result is a DN) come in handy.
But wait: ldapdn can only handle single results. Bummer! Luckily I found this post.

Thanks to the memberOf attribute we have an automatic "reverse resolution" of groupmembership. The inner query gets the DN of the object that has the
mailaddress in question in its mail attribute (the group). The outer query gets the mailadress of every object (ldapm because we expect that to be more than
one) that has the DN we just got in its memberOf attribute (the group members).

zarafa_groups:
debug_print = "R: zarafa_groups LDAP lookup for \$local_part@\$domain"
driver = redirect
domains = +local_domains
condition = \${lookup ldap {LDAPCRED ldap:///LDAPSEARCHBASE?mail?sub?(&(objectClass=group)(zarafaAccount=1)(mail=\${quote_ldap:\$local_part@\$domain}))}}
data = \${lookup ldapm{LDAPCRED ldap:///LDAPSEARCHBASE?mail?sub?(memberOf=\${lookup ldapdn{LDAPCRED ldap:///LDAPSEARCHBASE??sub?(mail=\${quote_ldap:\$local_part@\$domain})}})}}

LDAP Setup

I did not test this yet, but I think that you should be able to use the same router as for the AD Setup. It just depends on your openLDAP server having the
memberOf attribute as an overlay. Or you might not even need this if in your directory the member attribute contains the mailaddress of the members instead
of its DN. Then you would just need an ldapm lookup for the member attributes of the object that has the address in question in its mail attribute. Isn't
LDAP just fun to wrap your head around?

Delivery to users

At this point we should have gotten the primary mailaddress of at least one user that needs to get the mail.

LDAP Setup / Active Directory Setup

This is just the same for both AD and OpenLDAP: Find out if there is a user with the mailaddress in question and send the mail to the server.

zarafa_users:
debug_print = "R: zarafa_user LDAP lookup for \$local_part@\$domain"
driver = manualroute
domains = +local_domains
condition = \${lookup ldap {LDAPCRED ldap:///LDAPSEARCHBASE?mail?sub?(&(objectClass=person)(zarafaAccount=1)(mail=\${quote_ldap:\$local_part@\$domain}))}}
route_list = * ZARAFASERVER byname
self = send
transport = zarafa_lmtp

Of course if you are using a multiserversetup you need to find out to which node the mail should be delivered to and therefore check the ZarafaUserServer
attribute (and hope that this something DNS can handle).

zarafa_users:
debug_print = "R: zarafa_user LDAP lookup for \$local_part@\$domain"
driver = manualroute
domains = +local_domains
condition = \${lookup ldap {LDAPCRED ldap:///LDAPSEARCHBASE?mail?sub?(&(objectClass=person)(zarafaAccount=1)(mail=\${quote_ldap:\$local_part@\$domain}))}}
route_list = * "\${lookup ldap {LDAPCRED ldap:///LDAPSEARCHBASE?ZarafaUserServer?sub?(&(objectClass=person)(zarafaAccount=1)(mail=\${quote_ldap:\$local_part@\$domain}))}}" byname
self = send
transport = zarafa_lmtp

DB Setup

Since you can do only singleserver setups with the DB plugin you just need to check with the query we defined as a macro that there actually is a user that
has the mailaddress in question.

zarafa_user:
debug_print = "R: zarafa_user for \$local_part@\$domain"
cannot_route_message = "no such user"
driver = manualroute
domains = +local_domains
condition = \${lookup mysql{ZARAFA_USER} {1}{0}}
route_list = * ZARAFASERVER byname
self = send
transport = zarafa_lmtp

LMTP Transport

Finaly you need to create a transport that sends the mail to the zarafa-deliveryagent. Delivery via LMTP is preferable because this way the dagent can do singleinstance for messages that are delivered to more than one recipient.

zarafa_lmtp:
debug_print = "T: zarafa_lmtp for \$local_part@\$domain"
driver = smtp
allow_localhost = true
protocol = lmtp
port = 2003

And now lets check how this works out:

root@zcpadms01:\~\# exim -bt bsimpson@springfield.com  
R: zarafa_aliases LDAP lookup for bsimpson@springfield.com  
R: zarafa_aliases LDAP lookup for bart.simpson@springfield.com  
R: zarafa_groups LDAP lookup for bart.simpson@springfield.com  
R: zarafa_user LDAP lookup for bart.simpson@springfield.com  
Bart.Simpson@springfield.com   <-- bsimpson@springfield.com  
router = zarafa_users, transport = zarafa_lmtp  
host zcpadms01.skynet.private [192.168.56.28]

The alias router gets bart.simpson@springfield.com as the primary address for bsimpson@springfield.com and this address gets passed along all the routes from the top again. This time neither the alias router, nor the group ruter find a match since we already have a primary address. But the user router accepts the mail and delivers it via the lmtp transport. Success!

Now lets do the same for a group named kids:

root@zcpadms01:\~\# exim -bt kids@springfield.com  
R: zarafa_aliases LDAP lookup for kids@springfield.com  
R: zarafa_groups LDAP lookup for kids@springfield.com  
R: zarafa_aliases LDAP lookup for bart.simpson@springfield.com  
R: zarafa_groups LDAP lookup for bart.simpson@springfield.com  
R: zarafa_user LDAP lookup for bart.simpson@springfield.com  
R: zarafa_aliases LDAP lookup for lisa.simpson@springfield.com  
R: zarafa_groups LDAP lookup for lisa.simpson@springfield.com  
R: zarafa_user LDAP lookup for lisa.simpson@springfield.com  
Lisa.Simpson@springfield.com  <-- kids@springfield.com  
router = zarafa_users, transport = zarafa_lmtp  
host zcpadms01.skynet.private [192.168.56.28]  
Bart.Simpson@springfield.com  <-- kids@springfield.com  
router = zarafa_users, transport = zarafa_lmtp  
host zcpadms01.skynet.private [192.168.56.28]

We can see that the group router accepts the kids@springfield.com address, yields two new adresses (its members of course) that now each travel along our routers from top to bottom again. Since the groups router yielded primary addresses they get accepted by the users router and handed over to the lmtp transport.

Congratulations! You now got rid of postfix and can use Exim on your Zarafaserver

If you should find any errors I am of course highly interested in hearing from you!

Tagged as : AD Exim LDAP Zarafa