Multidomain mail system with Postfix & Cyrus
  1. 1. Other posts about Postfix and Cyrus
  2. 2. Postfix
  3. 3. Cyrus
  4. 4. Empty inbox
  5. 5. Useful links
Other posts about Postfix and Cyrus
  1. Your Own Mail Host – Postfix, Cyrus, FreeBSD
  2. Setting up SMTP authorization for Postfix using Cyrus SASL
  3. Setting up secure Cyrus
  4. Multidomain mail system with Postfix & Cyrus
  5. Cyrus & SASL – «No Mechanism Available»

After more than a year since the first time i had to setup my own mail host at i-forge.net I came across the need to do so again – but for another domain. In this guide I will explain how one can setup virtual domain system on FreeBSD 8 using Postfix and Cyrus 2.2.

I assume that you have already set up a working mail host that successfully handles incoming and outgoing messages for a single domain. If not, you can start from here.

Postfix

Let’s begin with the easiest part. In Postfix, there are at least 3 ways to set up a multidomain e-mail server; we’ll use an advanced virtual domain system that, however, is very easy to configure. In the docs this is called «Postfix virtual MAILBOX example: separate domains, non-UNIX accounts».

First we need to change the main configuration file – /usr/local/etc/postfix/main.cf. Add the following lines to it:

conf; comma-separated list of domains that Postfix must accept incoming mail for.
virtual_mailbox_domains = newdomain.net
; directory where inbox will be saved; similar to mail_spool_directory and
; can reside under it as well.
virtual_mailbox_base = /var/mail/vhosts
; these two point where Postfix must look for existing addresses and aliases.
virtual_mailbox_maps = hash:/usr/local/etc/postfix/vmailbox
virtual_alias_maps = hash:/usr/local/etc/postfix/virtual
; these 3 are somewhat obscure; I have left the first as it was in the docs...
virtual_minimum_uid = 100
; ...and these 2 are user and group IDs of the 'postfix' account.
virtual_uid_maps = static:125
virtual_gid_maps = static:152
; maximum size of a mail box; must be not less than message_size_limit.
virtual_mailbox_limit = 209715200
; this one is crucial - see "Empty inbox" section below.
virtual_transport=$mailbox_transport

Postfix docs contain excessive info on these configuration options so you can read up freely.

After changing the config file create the virtual_mailbox_base directory giving enough perms for Postfix to write there.

Now let’s specify which recipients our new domain has – edit /etc/postfix/vmailbox file (create it if it doesn’t exist):

conftheaddress@newdomain.net    newdomain.net/theaddress
chimp@newdomain.net         newdomain.net/chimp

Postfix docs say that the second column’s content (after the spaces) is ignored but I have somewhere seen the form used above and since everything works I don’t want to risk it. You can try to leave some junk there and see for yourself, though.

In /etc/postfix/virtual you can specify aliases that will route mail from the registered recipients (in vmailbox) – I don’t need this so the file is just empty.

After creating both vmailbox and virtual files you need to update their hash maps:

shpostmap /etc/postfix/vmailbox
postmap /etc/postfix/virtual

Now you only need to restart the daemon with shpostfix reload and the entire thing should just work. You can acknowledge this by sending a test e-mail to one of the addresses you have specified in vmailbox – if it doesn’t return with mail for newdomain.net loops back to myself message or other then Postfix has accepted it. Even without a working IMAP access you can check that it has stored it by looking inside /var/mail/vhosts/newdomain.net/theaddress.

If you’re having problems try increasing Postfix’ ((http://www.postfix.org/DEBUG_README.html#verbose service verbosity)), e.g. SMTPd, and checking the logs.

Cyrus

As it was the last time this guy is more tough; however, you should be able to avoid the pitfalls if you follow the instructions below.

There are two ways to make Cyrus serve mails for multiple domains from the single machine: by running two (three, etc. – as many domains as you have) instances of Cyrus and thus having multiple configs and mail directories, or using Cyrus’ built-in virtual domain system. The latter seems less hacky so I have chosen it. The former is explain in detail in this article.

The official Cyrus docs have a good (and concise) article on setting up virtual domains ([[http://www.opensource.apple.com/source/CyrusIMAP/CyrusIMAP-187/cyrus_imap/doc/text/install-virtdomains manpage version]]); check also this article.
First add these lines to the IMAP daemon’s config file (/usr/local/etc/imapd.conf):

conf; enables virtual domain system.
virtdomains: userid
; specifies which domain to use when an e-mail contains no "@" part;
; this should be the name of your MAIN domain, not the virtual one;
; it will also be used to qualify existing single-domain mailboxes.
defaultdomain: i-forge.net

In order to avoid strange login glitches it’s highly recommended to set virtdomains to userid instead of yes (or any other true value). On my host neither PLAIN nor LOGIN authentication worked until this was changed.

Virtual domains can have their own and global administrators – both types are specified in admins config option in imapd.conf but global administrators do not have the @... part. Example: admins: globaladmin virtadm@newdomain.net.

Restart Cyrus by doing sh/usr/rc.d/imapd restart.

Now you need to create user accounts with shsaslpasswd2:

shell$ saslpasswd2 -c theaddress@newdomain.net
Password:
Again (for verification):

Repeat for every Postfix recepient you have. Now let’s create the actual mailboxes with shcyradm (remember about segfaults – try different auth mechanisms) – note that the full e-mail address starts with user.:

shell$ cyradm --auth CRAM-MD5 -u <admin> localhost
Password:
localhost> cm user.theaddress@newdomain.net

If you have unixhierarchysep enabled then the prefix will be user/, not user..

You can list all domain’s mailboxes with lm *@newdomain.com.

Once again repeat for every listed recepient. After the above is done Cyrus will create /var/imap/domain/n/newdomain.net/user/t/theaddress directory (so check that its location is writable for user cyrus) that will contain IMAP/POP folders, quota and other data and work like /var/imap/user/a/address for non-virtual domains.

Now Cyrus should be configured – you can connect to it with your POP/IMAP client using newly created accounts.

Empty inbox

This one was troubling for several hours. I have configured everything and as far as I could see my mails were going just fine – virtual domains received them (at least there were no errors in the logs), mailing from them were also fine. However, Cyrus’ inbox has remained empty even though Postfix was storing incoming e-mails in its /var/mail/vhosts just fine.

After hard scratching of my head something has finally clicked inside; to avoid troubling you with tangled thoughts let’s just to the answer: virtual_transport.

Postfix has a flexible transport configuration; this means that it can deliver mail to various services. Transport is configured in /usr/local/etc/postfix/master.cf – it’s similar to /etc/inetd.conf.

However, note that Postfix doesn’t always use this file to determine how it will transfer mail to another agent – a particular configuration option can have a specific value. For example, on my system mailbox_transport is set to this:

confmailbox_transport = lmtp:unix:/var/imap/socket/lmtp

This means that Postfix will always send messages to the Unix socket at /var/imap/socket/lmtp – which is controlled by Cyrus. If you are using older Postfix or Cyrus this option might point to cyrus transport in master.cf.

Virtual domain system uses its own transport option – virtual_transport – and by default its value is virtual which means master.cf is used to look up the delivery method. Note that this happens only with virtual mail boxes so your old mailboxes will function as they did but virtual ones simply won’t show anything in Cyrus’ inboxes – because Cyrus is not contacted when new mail is received.

This is easy to fix now that we know the problem: set virtual_transport to the same value as mailbox_transport since we’re using Cyrus to handle all of our mailboxes. Postfix supports $configOption variables so you can avoid copying the option value by setting it to this:

confvirtual_transport=$mailbox_transport

Useful links