HOWTO: Multi-Domain Support in Kolab

Different Types of Multi-Domain Support

Kolab Groupware supports two different ways of providing supporting the use of multiple domains. To choose your right way you must know the difference between multi-domain and domain-aliasing or parent/child domains.

If you want some more background knowledge about Kolab, Domains and LDAP please follow this introduction: Parent, Alias and Child Domain Namespaces.

Alias/Child Domains

You’re responsible for several domains where you want to have multiple alias mail addresses assigned to you and/or your users. Everything happens within the same LDAP directory and usually you or your admins are taking care of managing the accounts.

A usual scenario would be:

You’ve a cool project/company domain like mydomain.com (which will be your primary domain) and you want to be able to receive/send emails for (other child/alias domains) mydomain.net, mydomain.org and myproject.com as well.

You then can add mail addresses/aliases to your user account and everything ends up in the users single mailbox.

Adding additional domains to your primary domain is fairely easy and doesn’t require to modify any configuration file at all.

  1. Log Into the Kolab-Webadmin
  2. Select “Domains” in the top navigation panel
  3. Select “mydomain.org” on the left domain list panel (the domain list should only contains one single domain)
  4. In the section “Domainname(s)” click [+] somewhere in the domain listing and fill in your myotherdomain.org
  5. Press save & use it.

Parent Domains/Multi-Domain

You run seperated LDAP directories which have absolutely nothing in common. No shared mail addresses, no shared users. Think about 2 different companies (microsoft.com and apple.com) that want to use the same kolab infrastructure but having everything seperated, don’t share anything at all and therefore manage their own accounts, mail addresses, folders, resources, etc. Every one of those seperated domains can have child/alias domains as well.

Here comes the multidomain howto into play which requires some background knowledge. Especially about postfix, ldap services, etc.

PLEASE do yourself a favor and read the next section Before You Start and all their references to fully understand the scenario.

Before You Start

For environments that could possibly end up hosting a lot of domains, it is recommended to set up a domain_base_dn that is contained within a database, as opposed to the default cn=kolab,cn=config.

For environments seeking to host system administrator account information and group membership in a centralized fashion, the use of a management domain – the use of the primary domain as a management domain – is recommended. Note that such a management domain can hold an organizational unit to hold the domain name spaces hosted, such as ou=Domains,$root_dn.

Environments that seek to provide highly available (multi-master) replicated LDAP environments for many domains should realize the default maximum number of semaphore sets is 128.

See also

Amavisd Changes

Amavis wishes to determine whether the email passed to it (through it) is to be received by a local recipient. However, it does not support lookups against LDAP – to see if domain name spaces are indeed locally hosted.

Only for (inbound or internal) traffic – to domain name spaces considered locally hosted – will Amavisd add the X-Spam-* headers. As such, it is necessary to tell Amavis what the local domain name spaces are, in order to retain any spam headers.

  1. Edit /etc/amavisd/amavisd.conf, setting @local_domains_maps.

    For dynamic environments, ...

    consider replacing the @local_domains_maps, with a default of:

    @local_domains_maps = ( [".$mydomain"] );
    

    for the following wildcard expression:

    $local_domains_re = new_RE( qr'.*' );
    

    This will simply match all domains, including outgoing traffic.

    For (relatively) static environments, ...

    you can maintain a list of domain name spaces in @local_domains_maps, noted that you’ll have to restart the amavisd service after each change.

    This would end up looking like:

    @local_domains_maps = ( [
            ".$mydomain",
            ".example.org",
            ".holding.inc"
        ] );
    

    Note

    You will need to add parent domain name spaces as well as alias domain name spaces to this list as separate items.

  2. Restart the service:

    # service amavisd restart
    

Cyrus IMAP Changes

Cyrus IMAP has, by default, been configured to allow users to login with a uid, mail or alias login username, translating that login username to the intended mailbox using a process called canonification.

For multi-domain deployments, additional configuration is added to make the process multi-domain aware (Kolab 3.2 and later), or avoid executing the process altogher (Kolab 3.1 and earlier).

Warning

Cyrus IMAP 2.5 (Kolab 3.2 and later)

Cyrus IMAP 2.5 ships with a patch created by Kolab Systems, and submitted and accepted upstream, that allows the parent domain DIT root dn to be discovered.

Add the following settings to imapd.conf(5) as needed, and restart the cyrus-imapd service:

ldap_domain_base_dn ""

The base dn to search for domain name spaces. In a default Kolab Groupware setup, the appropriate default is cn=kolab,cn=config – however we do not ship Cyrus IMAP with that as a default configuration value.

If this configuration option is not set, ptloader will not perform any discovery.

ldap_domain_filter (&(objectclass=domainrelatedobject)(associateddomain=%s))

The filter to use when searching for a domain name space.

For default Kolab Groupware setups, the default configuration value works as intended.

ldap_domain_name_attribute associatedDomain

The attribute to use when attempting to find the parent domain name space.

For default Kolab Groupware setups, the default configuration value works as intended.

ldap_domain_scope sub

The scope to use when searching. One of “sub”, “one”, “base”.

For default Kolab Groupware setups, the default configuration value works as intended.

ldap_domain_result_attribute inetdomainbasedn

The attribute name of which to use the value, if the attribute is at all present on entries found, that contains the domain name space DIT root dn.

For default Kolab Groupware setups, the default configuration value works as intended.

Warning

Cyrus IMAP 2.4

The following changes are needed only for Kolab Groupware product streams that ship Cyrus IMAP 2.4. At the time of this writing, that includes Kolab 3.1 and earlier versions, and Kolab Enterprise 13 and earlier versions of the enterprise edition.

This is not (yet) available for multi-domain deployments.

Execute the following sequence to remove the canonification process:

# sed -i \
    -e 's/^auth_mech/#auth_mech/g' \
    -e 's/^pts_module/#pts_module/g' \
    -e 's/^ldap_/#ldap_/g' \
    -e 's/auxprop saslauthd/saslauthd/' \
    -e '/ptloader/d' \
    /etc/cyrus.conf \
    /etc/imapd.conf

# service cyrus-imapd restart

Postfix Changes

Postfix has originally been configured to use the primary domain’s DIT root dn for LDAP lookups. So, for a system setup for example.org, all LDAP lookup tables are configured to lookup entries in dc=example,dc=org.

The relevant lookup tables have been written out to /etc/postfix/ldap/, and added to the relevant Postfix configuration settings (in order of application):

mydestination

Check if the SMTP server is supposed to be receiving email for the recipient domain.

This map (Kolab default: ldap:/etc/postfix/ldap/mydestination.cf) can remain largely unchanged, but we need two copies of it:

  1. Copy /etc/postfix/ldap/mydestination.cf twice:

    # cp /etc/postfix/ldap/mydestination.cf \
        /etc/postfix/ldap/hosted_duplet_mydestination.cf
    
    # cp /etc/postfix/ldap/mydestination.cf \
        /etc/postfix/ldap/hosted_triplet_mydestination.cf
    
  2. Edit /etc/postfix/ldap/hosted_duplet_mydestination.cf and change the query_filter setting to:

    query_filter = (&(objectclass=domainrelatedobject)(associateddomain=%s)(associateddomain=*.*)(!(associateddomain=*.*.*)))
    

    This map will be used to look up whether a domain name is a duplet of components (i.e. example.org, but not example.org.uk). This is needed for the templated search base we are going to use in other maps.

  3. Edit /etc/postfix/ldap/hosted_triplet_mydestination.cf and change the query_filter setting to:

    query_filter = (&(objectclass=domainrelatedobject)(associateddomain=%s)(associateddomain=*.*.*))
    

    This map will be used to look up whether a domain name is a triplet of components (i.e. example.org.uk, but not example.org). This is needed for the templated search base we are going to use in other maps.

local_recipient_maps

Check if the recipient is a valid local recipient.

The original map is at /etc/postfix/ldap/local_recipient_maps.cf.

  1. Copy /etc/postfix/ldap/local_recipient_maps.cf twice:

    # cp /etc/postfix/ldap/local_recipient_maps.cf \
        /etc/postfix/ldap/hosted_duplet_local_recipient_maps.cf
    
    # cp /etc/postfix/ldap/local_recipient_maps.cf \
        /etc/postfix/ldap/hosted_triplet_local_recipient_maps.cf
    
  2. Edit /etc/postfix/ldap/hosted_duplet_local_recipient_maps.cf, and replace the following two settings:

    1. search_base:

      search_base = dc=%2,dc=%1
      
    2. domain:

      domain = ldap:/etc/postfix/ldap/hosted_duplet_mydestination.cf
      
  3. Edit /etc/postfix/ldap/hosted_triplet_local_recipient_maps.cf, and replace the following two settings:

    1. search_base:

      search_base = dc=%3,dc=%2,dc=%1
      
    2. domain:

      domain = ldap:/etc/postfix/ldap/hosted_triplet_mydestination.cf
      
  4. Adjust the Postfix local_recipient_maps setting to match the new lookup tables (line breaks for legibility):

    # postconf -e "local_recipient_maps=\
        ldap:/etc/postfix/ldap/hosted_triplet_local_recipient_maps.cf,\
        ldap:/etc/postfix/ldap/hosted_duplet_local_recipient_maps.cf"
    

virtual_alias_maps

Translate original recipient address in to one or more target recipient addresses.

This applies to, for example, a user john.doe@example.org with a secondary mail address of doe@example.org. virtual_alias_maps are responsible for making sure inbound traffic for doe@example.org ends up in the mailbox for john.doe@example.org.

The virtual_alias_maps lookup tables are configured such that individual users, mail addresses to be forwarded elsewhere [1], mail-enabled distribution groups (static and dynamic), shared folders and possibly catchall addresses [2] are delivered to the correct mailbox(es).

  1. Copy the original virtual alias maps lookup tables twice, each:

    # for map in virtual_alias_maps \
            virtual_alias_maps_mailforwarding \
            virtual_alias_maps_sharedfolders \
            mailenabled_distgroups \
            mailenabled_dynamic_distgroups \
            virtual_alias_maps_catchall; do
    
        [ ! -f "/etc/postfix/ldap/${map}.cf" ] && continue
    
        cp /etc/postfix/ldap/${map}.cf \
            /etc/postfix/ldap/hosted_duplet_${map}.cf
    
        sed -r -i \
            -e 's|^search_base = .*$|search_base = dc=%2,dc=%1|g' \
            -e 's|^domain = .*$|domain = ldap:/etc/postfix/ldap/hosted_duplet_mydestination.cf|g' \
            /etc/postfix/ldap/hosted_duplet_${map}.cf
    
        cp /etc/postfix/ldap/${map}.cf \
            /etc/postfix/ldap/hosted_triplet_${map}.cf
    
        sed -r -i \
            -e 's|^search_base = .*$|search_base = dc=%3,dc=%2,dc=%1|g' \
            -e 's|^domain = .*$|domain = ldap:/etc/postfix/ldap/hosted_triplet_mydestination.cf|g' \
            /etc/postfix/ldap/hosted_triplet_${map}.cf
    
    done
    
  2. Adjust the Postfix virtual_alias_maps setting to match the new lookup tables (line breaks for legibility):

    # postconf -e "virtual_alias_maps=\$alias_maps,\
        ldap:/etc/postfix/ldap/hosted_triplet_virtual_alias_maps.cf,\
        ldap:/etc/postfix/ldap/hosted_duplet_virtual_alias_maps.cf,\
        ldap:/etc/postfix/ldap/hosted_triplet_virtual_alias_maps_mailforwarding.cf,\
        ldap:/etc/postfix/ldap/hosted_duplet_virtual_alias_maps_mailforwarding.cf,\
        ldap:/etc/postfix/ldap/hosted_triplet_virtual_alias_maps_sharedfolders.cf,\
        ldap:/etc/postfix/ldap/hosted_duplet_virtual_alias_maps_sharedfolders.cf,\
        ldap:/etc/postfix/ldap/hosted_triplet_mailenabled_distgroups.cf,\
        ldap:/etc/postfix/ldap/hosted_duplet_mailenabled_distgroups.cf,\
        ldap:/etc/postfix/ldap/hosted_triplet_mailenabled_dynamic_distgroups.cf,\
        ldap:/etc/postfix/ldap/hosted_duplet_mailenabled_dynamic_distgroups.cf,\
        ldap:/etc/postfix/ldap/hosted_triplet_virtual_alias_maps_catchall.cf,\
        ldap:/etc/postfix/ldap/hosted_duplet_virtual_alias_maps_catchall.cf"
    

transport_maps

Use the outcome of virtual_alias_maps to determine the final delivery protocol and target.

For local mailboxes, and in a default Kolab Groupware setup, this tends to be lmtp:unix:/var/lib/imap/socket/lmtp.

  1. Copy the original transport maps lookup table twice:

    # cp /etc/postfix/ldap/transport_maps.cf \
            /etc/postfix/ldap/hosted_duplet_transport_maps.cf
    
    # cp /etc/postfix/ldap/transport_maps.cf \
            /etc/postfix/ldap/hosted_triplet_transport_maps.cf
    
  2. Replace the same settings search_base and domain:

    # sed -r -i \
        -e 's|^search_base = .*$|search_base = dc=%2,dc=%1|g' \
        -e 's|^domain = .*$|domain = ldap:/etc/postfix/ldap/hosted_duplet_mydestination.cf|g' \
        /etc/postfix/ldap/hosted_duplet_transport_maps.cf
    
    # sed -r -i \
        -e 's|^search_base = .*$|search_base = dc=%3,dc=%2,dc=%1|g' \
        -e 's|^domain = .*$|domain = ldap:/etc/postfix/ldap/hosted_triplet_mydestination.cf|g' \
        /etc/postfix/ldap/hosted_triplet_transport_maps.cf
    
    done
    
  3. Adjust the Postfix virtual_alias_maps setting to match the new lookup tables (line breaks for legibility):

    # postconf -e "transport_maps=hash:/etc/postfix/transport,\
        ldap:/etc/postfix/ldap/hosted_triplet_transport_maps.cf,\
        ldap:/etc/postfix/ldap/hosted_duplet_transport_maps.cf"
    

    Note

    Note that hash:/etc/postfix/transport is used to map shared@ email addresses to the LMTP socket for local delivery, while the default option for local_transport remains local:$myhostname (meaning local delivery to /var/spool/mail/$user).

For each parent domain that holds an alias domain name space, you are required to create a copy of each of the configured mydestination, local_recipient_maps, virtual_alias_maps and transport_maps lookup tables, and adjust its settings to match the parent domain name space and alias domain name spaces.

If you don’t, a hosted_duplet lookup for example.org might succeed if the root dn for the organizations directory information tree is indeed dc=example,dc=org, but a lookup for alias domain name spaces that also need to be looked up against dc=example,dc=org will fail – a lookup for a recipient in an alias domain name space of example.com would end up as occurring against dc=example,dc=com, which may or may not exist, but is most definitely not the same tree as dc=example,dc=org.

Note

Please note that developments are underway to configure referrals for this type of setup.

A set of tables for a parent domain name space of example.org holding alias domain name spaces example.com and example.de for example would look as follows (three sample files included):

/etc/postfix/ldap/example.org/mydestination.cf:

server_host = localhost
server_port = 389
version = 3
search_base = cn=kolab,cn=config
scope = sub

bind_dn = uid=kolab-service,ou=Special Users,dc=example,dc=org
bind_pw = Welcome2KolabSystems

query_filter = (&(associatedDomain=%s)(associatedDomain=example.org))
result_attribute = associateddomain

/etc/postfix/ldap/example.org/local_recipient_maps.cf:

server_host = localhost
server_port = 389
version = 3
search_base = cn=kolab,cn=config
scope = sub

domain = ldap:/etc/postfix/ldap/example.org/mydestination.cf

bind_dn = uid=kolab-service,ou=Special Users,dc=example,dc=org
bind_pw = Welcome2KolabSystems

query_filter = (&(|(mail=%s)(alias=%s))(|(objectclass=kolabinetorgperson)(|(objectclass=kolabgroupofuniquenames)(objectclass=kolabgroupofurls))(|(|(objectclass=groupofuniquenames)(objectclass=groupofurls))(objectclass=kolabsharedfolder))(objectclass=kolabsharedfolder)))
result_attribute = mail

/etc/postfix/ldap/example.org/virtual_alias_maps.cf:

server_host = localhost
server_port = 389
version = 3
search_base = dc=example,dc=org
scope = sub

domain = ldap:/etc/postfix/ldap/example.org/mydestination.cf

bind_dn = uid=kolab-service,ou=Special Users,dc=example,dc=org
bind_pw = Welcome2KolabSystems

query_filter = (&(|(mail=%s)(alias=%s))(objectclass=kolabinetorgperson))
result_attribute = mail

Shared Folders Transport Maps

If you plan to use shared folders for hosted domains you currently have to add a transport rule for each parent domain (no alias/child domain) manually to /etc/postfix/transport call postmap /etc/postfix/transport afterwords and reload postfix.

shared@example.org      lmtp:unix:/var/lib/imap/socket/lmtp
shared@apple.com        lmtp:unix:/var/lib/imap/socket/lmtp
shared@microsoft.com    lmtp:unix:/var/lib/imap/socket/lmtp

Currently there’s no automated process or ldap equivalent configuration for it.

Roundcube Changes

Roundcube too, by default, is configured to only operate against the primary domain.

The settings most relevant to allowing authentication to succeed is in /etc/roundcubemail/kolab_auth.inc.php. At or near line 11, the base_dn settings for the kolab_auth_addressbook needs to be configured such that it uses the %dc placeholder (that Roundcube will substitute for the correct root dn for the domain), using the added domain_* settings:

$config['kolab_auth_addressbook'] = Array(
        (...snip...)
        'base_dn'                   => 'ou=People,%dc',
        (...snip...)
        'groups'                    => Array(
                'base_dn'           => 'ou=Groups,%dc',
        (...snip...)
        'domain_base_dn'            => 'cn=kolab,cn=config',
        'domain_filter'             => '(&(objectclass=domainrelatedobject)(associateddomain=%s))',
        'domain_name_attr'          => 'associateddomain',
        (...snip...)

You should perform the same for the ldap_public address book configuration in /etc/roundcubemail/config.inc.php.

Footnotes

[1]mail forwarding
[2]catchall addresses