Virtual users with Exim and Courier IMAP

This page is now out of date and is no longer being updated. It’s left here for posterity, or until I can be bothered to take it down.

Previously I’ve had lots of success with the odd one or two local users on my system, but I’ve always wanted to run along with a mix of virtual users/domains so I can host other friend’s mail and websites, but without having to give them shell access.

The first stage is getting Exim is to deliver mail properly:

Create a directory under which you will have sub directories for each of your virtual domains and then under those each virtual user. In my case, I created /home/courier, so I would have a directory structure like/home/courier/example.com/user1/ /home/courier/example.com/user2/ etc. Exim will take care of creating all this directory structure for you as long as the permissions are correct – as you’ll see later.

A list of all virtual users will be kept in separate files for each domain. Create a file called virtual_users_$domain (e.g. virtual_users_example.com) then just add a virtual user per line.

Modify your Exim configuration to include a virtual users router and transport.

Add this just below your localuser router:

# This router matches virtual_users mailboxes

virtual_user:
driver = accept
domains = +local_domains
local_parts = lsearch:/etc/virtual_users_$domain
transport = virtual_localuserdelivery

Add this anywhere in your transports section (order is irrelevant):

# This transport is for virtual user delivery after checking for local delivery

virtual_localuserdelivery:
driver = appendfile
user = courier
maildir_format
directory = /home/courier/$domain/$local_part
create_directory
delivery_date_add
envelope_to_add
return_path_add
group = mail
mode = 0660

Now – you remember that earlier I mentioned the need to get permissions right? Well, this is where it all shakes out. In my case I have a local user called courier which is the user under which Courier IMAP runs, so the virtual user directory is owned by the courier user and is part of the group mail. Hence you need to ensure that the user and group in the virtual user transport are set so that Exim can deliver mail and create new directories on the fly for new virtual users and domains.

That should be that – restart Exim then try sending some test mail and ensure it ends up where you think it should!

I also have a separate external aliases router that goes above the localuser and virtualuser routers. If you want a catch all for your virtual domain, stick that in here, redirecting to the virtual user of your choice.

There are also other things needed in order to get virtual user .forward and vacation messages working, but I’m going to leave that for another day.

If you have SpamAssassin setup in the same way that I do (see my other section on this topic), then you’ll need to make a slight change to your spamcheck router configuration and order. Make sure the spamcheck router appears after the two external mail routers (lookuphost and ipliteral) and comment out > check_local_user The reason for doing this is that virtual users aren’t local users (they don’t have an entry in NIS or passwd files), hence spamassassin won’t be run on virtual users unless you do this. As long as the order is routers is correct then outgoing mail (including mail you relay for as a backup MX) as well as mail you’ve injected into the queue with webmail or authenticated SMTP, won’t be spam checked:

begin routers

lookuphost:
driver = dnslookup
domains = ! +local_domains
transport = remote_smtp

literal:
driver = ipliteral
domains = ! +local_domains
transport = remote_smtp
no_more

# SpamAssassin
spamcheck_router:
no_verify

##!!## Comment out check_local_user when using virtual domains

# check_local_user

##!!##

# When to scan a message :
# - it isn't already flagged as spam
# - it isn't already scanned
condition = "${if and { {!def:h_X-Spam-Flag:} {!eq {$received_protocol}{spam-scanned}}} {1}{0}}"
driver = accept
transport = spamcheck

Now that mail is being delivered correctly, you need to configure Courier IMAP to pick the mail up and serve it to the client. All you need to do is create your virtual users – no messing around with user accounts as before.

cd /usr/lib/courier/sbin
./pw2userdb

run:
./userdb -f /etc/userdb "user@example.com" set home=/home/courier mail=/home/courier/example.com/user uid=UUU gid=GGG

then run:
./userdbpw | ./userdb -f /etc/userdb "user@example.com" set imappw

[and enter the password you want to use for the IMAP account when prompted]

finally:
./makeuserdb