Postfix + Courier + LDAP virtual domains HOWTO

Revisions:
  1. So What's this here all about ?

    For quite some time now I've been looking for a setup that will alow me to host serveral mail domains on the same mail server. unlike web hosting, this is not as easy as it sounds. the main problem is that while SMTP daemons and MTAs (Mail Transport Agents) are quite capable of handling mail delivery to different domains, IMAP (and POP3 if you want) doesn't really have any good mechanism for handling multiple domain logins.

    I've looked and tried several of the HOWTOs and HOWNOTs on the web, trying MySQL based stuff mostly, and nothing clicked for me - the solutions were often akward and were never 1,2,3 easy.

    So I wrote my own. I chose the specific software for different reasons - OpenLDAP as its the only free LDAP server (that I know of), Courier as it's the easiest to configure of the few that I checked and Postfix as I'm used to it - its my MTA of choise for several years now.

    Now - I'm not promising this will be any easier then other virtual domains HOWTOs out there. it was easy for me get the geese of - it took me just several hours to get the inital setup going, w/o knowing anything on LDAP, when most of it was spent on finding a good LDAP Browser..

    I will try to make it easier by describing everything, not taking anything for granted - so this HOWTO should be newbie friendly. as usual, if you find any mistakes, or want to make suggestions for improvments or any kind of comment - just go ahead and email me to the address down here.

    Oded Arbel
    oded@geek.co.il

  2. Ingridients

    Before we get down to the actual software involved (most of which you can really guess from the title :-), I want to stress how much choosing a good distribution is important for the ease of setup and use. if I forgot to mention it, I'm using Linux for this system - while it is quite possible to setup the same system on other unices, I know and use Linux so this is what I tested with - for other OSs the steps required may be different, and I'd love to accept notes on how to make this HOWTO "cross platform". Now, on to the distribution: I'm using Mandrake Linux 9 for my setup, which make is easy for me as all the software you'll need for this setup is already available in Mandrake Linux - either on the distribution itself or on one of the contrib CDs. to stress the former point further - steps described in this HOWTO will probably be slightly different in other distros.

    So what will we have today? these are the versions of the software I'm using:

    • Mandrake Linux version 9.0
    • Postfix version 1.1.11
    • Courier IMAP version 1.5.2
    • OpenLDAP version 2.0.25
    • Apache version 2.0.40
    • PHP version 4.2.3

    Optionally, I use the LDAP Browser/Editor by Jarek Gawor version 2.8.2 to set things up. once I finish the web interface, you will not need to use this specific piece of software, but it is a very good LDAP browser - best I've seen so far - and its written in Java, so you can have your non-Linux sysadmins use it too :-).

  3. The setup

    Let's assume you did get Mandrake Linux 9 installed (or any other OS you'd like to use) and you do know how to install and remove software packages. I know I promised to be describe everything, but I'll have to skip this part, because (a) there are tons of HOWTOs on the net to do that, not to mention the OSs own manuals and online help, (b) if you can't handle that then you should probably postpone building a mail server until you can do basic stuff with your OS of choice, and (c) it's boring :-)

    So what we'll need to do here is to make sure that we have all the necessary software packages installed:


  4. Configuring LDAP

    The first thing we'll configure is the OpenLDAP server - slapd - as it's the heart of the system. We'll need to add our own schema to suppoprt the attribute we'll use. I call it the Postfix Virtual Domains schema and you can download the file from my site here, but it's also relativly short so I pasted it here:
    #
    # postfix-ldap v3 directory schema
    #
    # Postfix schema for using LDAP to manage Postfix virtual domains
    #
    # Created by: Oded Arbel 
    #
    # This schema depends on:
    #       - core.schema
    #
    
    # Attribute Type Definitions
    attributetype ( 1.3.6.1.4.1.7602.1.2.1.1 NAME 'mailLogin'
            DESC 'Login id for Courier IMAP'
            EQUALITY caseExactIA5Match
            SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
            SINGLE-VALUE )
    
    attributetype ( 1.3.6.1.4.1.7602.1.2.1.2 NAME 'mailDomain'
            DESC 'Domain id underwhich this user will login'
            EQUALITY caseExactIA5Match
            SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
            SINGLE-VALUE )
    
    attributetype ( 1.3.6.1.4.1.7602.1.2.1.3 NAME 'mailForward' 
            DESC 'Location of .forward files for this user' 
            EQUALITY caseExactIA5Match 
            SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 
            SINGLE-VALUE ) 
     
    attributetype ( 1.3.6.1.4.1.7602.1.2.1.4 NAME 'mailStorage'
            DESC 'Path to the mail storage directory' 
            EQUALITY caseExactIA5Match 
            SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 
            SINGLE-VALUE ) 
    
    attributetype ( 1.3.6.1.4.1.7602.1.2.1.5 NAME 'mailHomeDir' 
            DESC 'Base directory for user specific files' 
            EQUALITY caseExactIA5Match 
            SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 
            SINGLE-VALUE ) 
     
    attributetype ( 1.3.6.1.4.1.7602.1.2.1.6 NAME 'mailDir'
            DESC 'Relative path from the mailHomeDir to the mailStorage' 
            EQUALITY caseExactIA5Match
            SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
            SINGLE-VALUE )
    
    # objectClass definitions
    objectclass ( 1.3.6.1.4.1.7602.1.2.2.1 NAME 'postfixUser'
            DESC 'Postfix-LDAP User' SUP top AUXILIARY
            MAY ( mail $ uid $ mailLogin $ mailDomain $
                  mailForward $ mailStorage $ mailHomeDir $
                  mailDir )
            )
    

    Save this to /usr/share/openldap/schema/postfix.schema. Now travel to /etc/openldap and open slapd.conf for editing. you'll need to add the new schema file to the list of the includes at the top of the file, by adding the line
    include /usr/share/openldap/schema/postfix.schema
    somewhere in the include list.
    other things that need changing from the default that came with my system:

    the last thing we need to setup is the ACL (Access Control Lists). your slapd.conf should have two enteries at the bottom, one 'access to attr=userPassword' and another 'access to *'. otherwise, you may be able to find them in slapd.access.conf or you'll need to create them. it should look like this (remember to change the DN suffixes to suit your basedn):
    # Basic ACL
    access to attr=userPassword
        by self write
        by anonymous auth
        by dn="uid=root,cn=Administrators,ou=People,dc=geek,dc=co,dc=il" write
        by dn="cn=courier,ou=Services,dc=geek,dc=co,dc=il" read
        by * none
    
    access to *
        by dn="uid=root,cn=Administrators,ou=People,dc=geek,dc=co,dc=il" write
        by * read
    

    Note: the rootdn defined earlier has full write access everything. also, a 'courier' DN (which is not in the default example, so make sure to put it in) has access to read the userPassword. this is necessary in order for Courier-IMAP to authenticate users since Courier-IMAP does not support the 'auth' type access.

    All that's left now is to start the OpenLDAP server by running service ldap restart. you can user 'start' instead of 'restart' if the server wasn't running by default, but using 'restart' is safe even if the server isn't running already so it's better to use that. if the server wasn't running, which you can tell if when doing 'Stopping slapd' the result was 'FAILED', then you may need to make sure that the OpenLDAP server is running by default. There are several ways to do this, but probably the easiest is to run ntsysv from the command line, looking for 'ldap' in the list and making sure its turned on. with Mandrake and an X session, you can also start 'Mandrake Control Center' and then goto system->services.

  5. Setting up the directory

    The next thing on the schedule is to build the actual directory. I have a basic directory structure that works for the virtual domains and looks to me to be easily expandable to allow other features, such as PAM authentication (haven't tried that), so I suggest you use that to base your directory on. it looks like this:
    rootdb 
      +-- Services 
      |     +-- Courier 
      |     +-- Other services that need authentication.. 
      +-- People 
      |     +-- Administrators 
      |     |     +-- root 
      |     |     +-- Other administration accounts.. 
      |     +-- Other user groups.. 
      +-- Mail Domains 
            +-- Geek 
            |     +-- Oded Arbel 
            |     +-- Other users on geek.co.il.. 
            +-- Other virtual domains..
    

    The 'Services' group is used to hold authentication accounts for software that needs to use the directory in authenticated mode (for example - to read and write user passwords). the 'People' group is used to hold non-virtual user accounts - currently only the root account. and the 'Mail Domains' group is used to hold all the information regarding the virtual domains, including virtual user accounts.

    As you will see later, each virtual domain in the setup has three identifiers which should be related (so as not to confuse the users), but need not be identical:

    To build the directory after you first start the server, you need to import an LDIF file. to start you up, I include here an LDIF with the basic directory structure described above, with the courier account, the root account and a sample virtual domain and virtual user. the passwords are already in (as the schema requires it) and are set as '123456' (without the quotes, of course). you can change the passwords after you import the LDIF with a LDAP browser, or before you import the LDIF by using the slappasswd utility and then copying and pasting the resulting text, including {SSHA} header, instead of the content of the userPassword fields.
    Also, don't forget to change every instance of 'dc=geek,dc=co,dc=il' to the base DN of your choice.

    dn: dc=geek,dc=co,dc=il
    dc: geek
    objectClass: dcObject
    objectClass: organization
    o: Geek
    
    dn: ou=Services, dc=geek,dc=co,dc=il
    ou: Services 
    objectClass: top 
    objectClass: organizationalUnit 
     
    dn: cn=courier,ou=Services, dc=geek,dc=co,dc=il 
    userPassword: {SSHA}QWxho4T9Uv5fLaunTGScZYogRvxJCV15 
    objectClass: top 
    objectClass: applicationProcess
    objectClass: simpleSecurityObject 
    description: Courier IMAP
    cn: courier 
     
    dn: ou=People, dc=geek,dc=co,dc=il 
    ou: People 
    objectClass: top 
    objectClass: organizationalUnit 
     
    dn: cn=Administrators,ou=People, dc=geek,dc=co,dc=il 
    gidNumber: 0 
    objectClass: top 
    objectClass: posixGroup 
    description: System Administrators 
    cn: Administrators 
     
    dn: uid=root,cn=Administrators,ou=People, dc=geek,dc=co,dc=il 
    sn: Administrator 
    userPassword: {SSHA}QWxho4T9Uv5fLaunTGScZYogRvxJCV15 
    loginShell: /bin/bash 
    uidNumber: 0 
    gidNumber: 0 
    objectClass: top 
    objectClass: person
    objectClass: posixAccount
    objectClass: organizationalRole
    uid: root
    cn: Administrator
    homeDirectory: /root
    description: System Administrator
    
    dn: ou=Mail Domains, dc=geek,dc=co,dc=il
    ou: Mail Domains
    objectClass: top
    objectClass: organizationalUnit
    
    dn: ou=Geek,ou=Mail Domains, dc=geek,dc=co,dc=il
    mail: geek.co.il
    uid: geek
    ou: Geek
    objectClass: top
    objectClass: organizationalUnit
    objectClass: postfixUser
    
    dn: cn=Oded Arbel,ou=Geek,ou=Mail Domains, dc=geek,dc=co,dc=il
    objectClass: top
    objectClass: person
    objectClass: postfixUser
    cn: Oded Arbel
    sn: Oded Arbel
    uid: oded
    userPassword: {SSHA}QWxho4T9Uv5fLaunTGScZYogRvxJCV15
    mail: oded@geek.com
    mail: odeda@geek.com
    mailLogin: geek.oded
    mailDomain: geek
    mailHomeDir: /var/spool/mail/geek.oded
    mailForward: /var/spool/mail/geek.oded/forward
    mailStorage: /var/spool/mail/geek.oded/mail/
    mailDir: mail
    

    Note: each object's 'dn' must end with the directory's suffix, as setup in the configuration file and other places - this must remain the same no matter what Virtual Domain you are putting in.

    Looking at the sample user you can notice several things which are important to how mail is routed and users authenticate:


    I'll expand about the directory structure later.

    To import this LDIF to the directory, save it to a file first. now pay close attention to use an editor that does not leave trailing spaces after the end of lines - this will break the import and is probably the number one reason for failed imports. now run the ldapadd utility like this:
    ldapadd -x -D "uid=root, cn=Administrators, ou=People, dc=geek, dc=co, dc=il" -W -f import.ldif
    Don't forget to change the -D entry to your real 'rootdn'. the program will ask for the rootdn password and then start to parse and add the entries to the directory. a successful run's output would look something like this:
    adding new entry "dc=geek,dc=co,dc=il"
    adding new entry "ou=Services, dc=geek,dc=co,dc=il"
    adding new entry "cn=courier,ou=Services, dc=geek,dc=co,dc=il"
    adding new entry "ou=People, dc=geek,dc=co,dc=il"

    etc'..

    If the import went well, then that's it - the directory server should be ready now - no need to restart it after updating the directory. if the LDIF import went successfuly that means you can "bind" to the directory with the "rootdn" account, so all we have left to do is to see if you can also bind with the Courier IMAP account. its important to have a demo account in the system now, so we can check if Courier will be able to see its password. to test we'll run the ldapsearch utility to issue a lookup for the demo account. if you have not changed the demo account's uid, then you'll need to run this:

    ldapsearch -x -b "dc=geek,dc=co,dc=il" -D "cn=courier, ou=Services, dc=geek, dc=co, dc=il" -W "(&(uid=oded))"
    Don't forget to change the base DN in the -b and -D parameters. the software will then ask for the courier's daemon password (as entered for the 'courier' account in the directory) and if entered correctly, should output all the attributes of the demo user account - including the 'userPassword' attribute. its content may not look like what you entered in the import, but if the attribute itself is there, then everything is ok.

  6. Setting up Postfix

    Setting up postfix is much simpler. open the main.cf configuration file of postfix for editing - under Mandrake Linux it will be in /etc/postfix/main.cf, but your mileage may vary. in all the next settings, you should never put the values in quotes, but I will write them here quoted, so it'll be clearer what needs to be typed in.

    first check the following enteries:
    We will also need to add to the main.cf file the LDAP configuration. I usually do it at the bottom of the file. the needed configuration lines are these :
    virtual_maps = ldap:ldapvirtual
    local_recipient_maps = ldap:ldapvirtual
    alias_maps = hash:/etc/postfix/aliases, ldap:ldapdeliver
    forward_path =
      /var/spool/mail/$user/forward/forward-${recipient_delimiter}${extension},
      /var/spool/mail/$user/forward/forward
    
    ldapvirtual_server_host = localhost
    #ldapvirtual_server_port = 389
    ldapvirtual_search_base = dc=geek, dc=co, dc=il
    #ldapvirtual_timeout = 10
    ldapvirtual_query_filter = (mail=%s)
    ldapvirtual_result_attribute = mailLogin
    ldapvirtual_bind = no
    
    ldapdeliver_server_host = localhost
    #ldapdeliver_server_port = 389
    ldapdeliver_search_base = dc=geek, dc=co, dc=il
    #ldapdeliver_timeout = 10
    ldapdeliver_query_filter = (mailLogin=%s)
    ldapdeliver_result_attribute = mailStorage
    ldapdeliver_bind = no
    

    make sure to set the correct server_host and server_port settings in both LDAP mappings (the example server_host setting of 'localhost' should be ok if you are running both the postfix server and the ldap server on the same computer. you can also leave the example commented out setting of server_port if you have not changed the port the LDAP server is running on and it is using the default LDAP non-SSLed port) and also change the search_base setting in both to fit your chosen base DN.

    A short explenation is in order. when Postfix received an email for a domain that does not fall into the mydestination setting, it will try it through the virtual_maps. quering the directory server for an object with a 'mail' attribute to match (using the ldapvirtual map) it will look for an object match and get the 'mailLogin' attribute's value. treating that value as an email address it will try to delivery it locally by first mapping it against the alias_maps. using the same attribute and value returned from the last search it will query the directory server again and will fetch the 'mailStorage' attribute which points to a maildir style mail storage (hence the trailing / in the sample user's 'mailStorage' attribute). The Postfix local delivery agent will then store that mail in that directory
    The forward_path entry is responsible for finding "forward" files for each user. these will be stored in a different directory and its content can be updated using some other kind of interface. the "forward" files are a nice postfix mechanism to allow users to control what to do with email they receive, for example : forward it to a different address, dump it in a file, etc'.

    After we're done with configuring Postfix, you can restart it using service postfix restart. this is not necessary as Postfix re-reads its configuration files automaticly if they were changed, but by calling 'restart' explicitly you will get notified if there are problems with the configuration. don't forget to make sure that Postix is started by default on your system.

  7. Setting up Courier IMAP

    Setting up Courier IMAP is the easiest part - go to the courier imap configuration directory, in Mandrake Linux it's /etc/courier, and edit authldaprc, setting the following:

    If you need to have Courier IMAP connect to the directory server over SSL, which is especially a good idea if the directory server is not on the same computer as the one the Courier IMAP daemon is running on, then you need to change LDAP_PORT to 636 and also set LDAP_TLS to 1 (I haven't tested that yet).

    Now save and restart the Courier IMAP daemons running service courier-imap restart. also don't forget to make sure that Courier IMAP is started by default on your system.

  8. Wrap up

    It's recomended to pre-create the directories for each virtual user. Postfix will create the directories when it first delivery mail there, but if a user will try to login before receiving any mail, when the directory still does not exist, the login will fail.
    Continuing the assumption that mail will be stored under /var/spool/mail by the user 'mail', you need to run the following commands:
    mkdir -p /var/spool/mail/<mailLogin>/forward
    maildirmake /var/spool/mail/<mailLogin>/mail
    chown mail -R /var/spool/mail/<mailLogin>
    
    while replacing <mailLogin> with the mailLogin attribute from each user account.