3. System Architecture

This sections describes which servers were used and how they all fit together at a system level.

3.1. Software Selection

Choosing one piece of software over another can often become the place of holy wars on the scale of emacs vs. vi. One could write a paper debating the merits of each of the SMTP servers out there, for example. This section briefly describes our choices for an SMTP server, an IMAP server, an LDAP server, and webmail software.

3.1.1. Postfix

One of the most important aspects of our MTA/MDA was that it would easily support virtual users. The other one was that it would allow local users to continue to use procmail[17] for their filtering to keep their mailboxes in order. We also wanted to keep the configuration easy for the administrator. Postfix[14] fits the bill quite nicely.

 

Postfix is my attempt to provide an alternative to the widely-used Sendmail program. Postfix attempts to be fast, easy to administer, and hopefully secure, while at the same time being Sendmail compatible enough to not upset your users.

 
-- Wietse Venema [13]  

Postfix is designed modularly with many small programs working together to fill the mail service role. Most of Postfix runs as a non-root user and most parts of Postfix can run in a chroot environment. While still a relatively new mailer, it has a design that lends itself to long term security. Its configuration is straight-forward and very human readable. All of the parameters are of the "key = value" variety.

Starting with Postfix 1.1.x a virtual user mail delivery agent has been included. This makes delivery to virtual mailboxes very easy with only a few lines added to the config. It's also very easy to integrate LDAP into Postfix. Another benefit is the fact that it was made to be a drop in replacement for Sendmail. So, if a program is designed to work with Sendmail, it'll most likely work with Postfix.

It should also be noted that Sendmail [22], Qmail [18], or Exim [2] may work in this situation as well. We just had a greater familiarity and confidence with Postfix for this task. A lot of the rest of what is described in this document will apply to the other MTAs at a high level, but your mileage may vary when it comes to implementation.

3.1.2. Courier-IMAP

There are numerous IMAP servers for Unix available, the most popular being UW IMAP from the University of Washington [24]. UW IMAP, however, does not support the Maildir format nor does it support virtual users. Thus, we decided to use Courier-IMAP [1], a popular alternative IMAP server that works only with Maildir format.

Native support for Maildir was the original attraction to Courier, but it also has good support for virtual users, LDAP, and IMAP over SSL. It also integrates nicely with procmail since procmail can deliver to Maildir directly, too.

I should point out that procmail can only work for local users and not virtual users. In practice this works out fine because virtual users have no way of editing the .procmailrc file since they do not have shell access. If you want procmail for both local and virtual users, you may have to choose a different IMAP server or a different method of integrating the pieces.

3.1.3. OpenLDAP

There's really only once choice for Open Source LDAP servers and that's OpenLDAP [12]. It is more than capable of handling everything we need, including support for LDAP over SSL and advanced authentication encryption algorithms such as salted SHA and salted MD5. There have been some doubts as to the scalability and stability of OpenLDAP for huge systems, but we have not experienced any problems. You may consider using a commercial LDAP server such as iPlanet's Directory Server [9], Novell's eDirctory [11], or OctetString's VDE Directory Suite [25], all of which run on Linux.

3.1.4. SquirrelMail

There are many choices of webmail systems. Some read directly from the file system, while others use IMAP or POP. We were recommended SquirrelMail [23], a PHP-based IMAP client, and it has worked out very well. Another webmail client that comes with good recommendations is IMP [7]. Be sure to check Freshmeat [3] for more options.

3.2. The Big Picture

Figure 1 shows how Postfix, Courier, Procmail, and OpenLDAP interact with each other. Postfix accepts incoming mail from SMTP and delivers it to the maildir mailboxes on the file system. It needs to have a list of valid users so it can bounce email for unknown users. It also needs to know where each user's mailbox is located on the file system so it can properly deliver messages. Postfix delivers the mail itself for virtual users and uses procmail as the MDA for local users.

Courier provides remote access to the maildir mailboxes via the IMAP and POP protocols. It needs to have a list of valid users and some means to authenticate them so users can log into their account. It, too, needs to know where each users' mailbox is located on the file system so it can read their messages.

Local user information can be accessed from the standard account database. A list of valid users can be obtained from /etc/passwd. The users' home directory, which can also be obtained from /etc/passwd, provides the location of the mailbox. Authentication can be handled by standard Unix mechanisms, such as pluggable authentication modules (PAM).

Virtual user information is stored in an LDAP directory since LDAP provides a mechanism for searching for valid users, getting user information, and authenticating users. It is possible to avoid an LDAP directory, but it will be more difficult to administer the virtual user information. For example, Postfix and Courier both support virtual users without LDAP using configuration files, but they have different file formats. It is possible to write scripts to keep these two sets of configuration files in sync, but it can be a chore and is error prone. Also, these file formats are not standard and require custom code to access them. LDAP is a much cleaner solution since it provides a standard interface for accessing the directory and many languages provided LDAP APIs.

Figure 1. Overall Design

3.3. Mailbox Location

Local users' mail is stored in their home directory at ${HOME}/Maildir/ in the maildir format. It is standard practice for maildir delivery to go into the users' home directory rather than /var/. Both Postfix and Courier work with this standard behavior out of the box.

Unlike local users, there is no standard location for virtual user's email. We chose to create a single Unix account, called vmail, that holds all the virtual mailboxes. [1] Postfix and Courier support looking up the directory and Unix account information out of LDAP. This document only describes how to setup this system with one user, though you are free to choose a different policy. Each virtual domain has a subdirectory within the ~vmail/domains/ directory. For example, if there is a user , mail would be stored in ~vmail/domains/example.com/john/ in maildir format.

3.4. LDAP Directory Design

There are many possibilities when designing your directory and not all aspects of this topic are covered here. One very useful reference is the iPlanet Deployment Guide [10]. This section also assumes you are familiar with LDAP concepts and terminology.

3.4.1. Tree Structure

You should take the time up front to design a tree that matches your specific requirements. There are many guidelines to take into consideration, but an important one is that entries should not change subtrees often, if at all. LDAP provides methods to change the attributes of an entry easily, but it is a little trickier to move an entry from one subtree to another.

Figure 2 shows a sample directory tree for a web hosting company. The company's domain name, myhosting.example, was chosen as the root suffix. Postfix and Courier search the o=hosting,dc=myhosting,dc=example subtree for email information. The o=accounts,dc=myhosting,dc=example subtree shows how you could integrate shell users for PAM into the same directory, but it is not necessary for setting up email. Each hosted domain gets its own organization beneath the hosting organization. Each email account goes under the domain's subtree. Thus, the distinguished name for the email address is:

mail=user2@domain2.example,o=domain2.example,o=hosting,dc=myhosting,dc=example

This is a fairly stable design as users will never transfer between domains. It is also quite flexible in that each domain's tree could be tailored, if needed. Each domain must have a postmaster entry that provides dual functionality. Its primary function is for access control, but it also acts as a forwarding email address. See Section 3.4.3 for more information on access control. Each domain must also have an abuse alias that forwards mail to the system administrator.

Figure 2. Sample LDAP Tree for a Web Hosting Company

3.4.2. Choosing a Schema

The schema defines which attributes an entry can have by defining object classes. None of the default schemas that come with OpenLDAP are really suited for entries used exclusively for email mailboxes or forwarding. We are using the schema that Courier provides in its distribution. Another possible schema to look at is the schema distributed with the qmail-LDAP project [19]. You can also design your own schema, but beware that you should use OIDs registered with the Internet Assigned Numbers Authority (IANA) [8].

3.4.2.1. Courier Schema

The courierMailAccount object class, summarized in Table 1, is used for email addresses that have an IMAP or POP account on the server. The courierMailAlias object class, summarized in Table 2, is used for email addresses that forward to another address.

AttributeRequiredDescription
mailYesThe full email address.
homeDirectoryYes The base directory where email is stored.
uidNumberYes The user ID of the account where messages are stored.
gidNumberYes The group ID of the account where messages are stored.
mailboxNo The mailbox in the home directory where messages are delivered.
uidNo The user name for this email address.
cnNo The common name for this email address.
gecosNoThe default GECOS.
DescriptionNoA description of this email address.
loginShellNoThe login shell for this user.
quotaNoThe quota for this user's email.
userPasswordNoEncrypted password.
clearPasswordNoClear text password.

Table 1. courierMailAccount

AttributeRequiredDescription
mailYesThe full email address.
maildropYes The email address to forward to. May be a local alias or a remote email address.
mailsourceNoMessage source.
DescriptionNoA description of this email address.

Table 2. courierMailAlias

The courierMailAccount object class does not exactly fit our needs. We do not need uidNumber and gidNumber since all mail goes to the vmail user. However, we must put in dummy values since the schema requires them [2]. We require the mailbox attribute since it is needed to determine the location of the mailbox on the file system. For this configuration the mailbox must end in a slash. This is a hint to postfix to use a maildir style mailbox. The userPassword attribute is also required since all email accounts are required to have a password in order to access them via IMAP or POP. We do not use the other optional attributes. Here is an example LDIF entry for :

dn: mail=user2@domain2.example, o=domain2.example, o=hosting, dc=myhosting,
 dc=example
objectClass: top
objectClass: courierMailAccount
mail: user2@domain2.example
homeDirectory: /home/vmail/domains
uidNumber: 101
gidNumber: 101
mailbox: domain2.example/user2/
userPassword: password

The courierMailAlias object class is a good fit for our needs. We only use the two required attributes and do not use either of the optional attributes. The maildrop attribute can be another email address or a local user on this machine. Here is an example LDIF entry for that forwards to :

dn: mail=user1@domain3.example, o=domain3.example, o=hosting, dc=myhosting,
 dc=example
objectclass: top
objectclass: courierMailAlias
mail: user1@domain3.example
maildrop: fred@otherdomain.example

It is possible to do many-to-many mappings of e-mail addresses to maildrops for the courierMailAlias. You just need to add multiple values for the mail and maildrop data elements. In the same way, the courierMailAccount can have a many-to-one mapping of e-mail addresses to mailbox.

3.4.3. Access Control

OpenLDAP provides many possibilities for access control. By default, the root account has read and write access to all entries in the tree. We would like to delegate some of this administration to individual users in each hosted domain so they can do minor changes on their own without access to the root account. This is done by making the postmaster entry an organizationalRole with a roleOccupant attribute for each entry with administration privileges. OpenLDAP can then be configured to allow access only to members of this group. Here is an LDIF example giving two users administrator privileges for domain2.example:

dn: cn=postmaster, o=domain2.example, o=hosting, dc=myhosting, dc=example
objectclass: top
objectclass: organizationalRole
cn: postmaster
roleOccupant: mail=user1@domain2.example, o=domain2.example, o=hosting,
 dc=myhosting, dc=example
roleOccupant: mail=user3@domain2.example, o=domain2.example, o=hosting,
 dc=myhosting, dc=example

See Section 4.3.1.5 for information on how to implement access control with OpenLDAP.

Notes

[1]

You may spread virtual users across multiple Unix accounts, too, for example, creating a Unix user for each virtual domain.

[2]

It should be noted that these values would be meaningful if we were spreading virtual users over many Unix users.