Cyrus, MySQL, and Multiple Domains

After creating my original method for multiple domains, I was very happy. However, in certain situations it may not work. For example, until I rewrite massaslpasswd, having users change their password requires logging in or writing something yourself. Additionally, it's completely incompatible with cyradm (and other web-admin-interfaces), so much so that it would be difficult even to write a front end. So if you need a front end, it is helpful if your data is in a database - I chose mysql.

Let me give a huge thank you to Kevin M. Myer who originally had the foresight to see Cyrus' ability to do this, and who shared his knowledge with me. I also want to give big thanks to Simon Loader for his mysql auxprop patch, and all his help.

It's important to note a few things: As usual, I'll point out some advantages and disadvantages:



First and foremost, we need to install SASLv2. This isn't as bad as it seemed. There's most likely a package for whatever OS you're using, but I installed from source so I could document the process.

Download Cyrus SASL 2.1.5 from here. Then download Simon Loader's patch from here.

Now make sure you have the following tools installed: autoheader, autoconf, automake, libtool. You will need all of them.

Extract the source and patch it like this:
$ tar zxvf cyrus-sasl-2.1.5.tar.gz
$ tar zxvf mysql+ldapauxprop.patch.tgz
$ cp ldap-mysql_auxprop_sasl-2\sasl_ldap_mysql.patch cyrus-sasl-2.1.5
$ cd cyrus-sasl-2.1.5
$ patch -p1 < sasl_ldap_mysql.patch

The only problem I get with the latest version of the patch is that ldapauxprop.c doesn't patch properly. This isn't a problem since we won't be using the ldap plugin.

Now it's time to prepare the source:
$ autoheader
$ autoconf
$ automake -i

Now you have ready source. Now we can configure. Your configure options may vary, but here were my configure options. So, decide what your configure line will be like and configure your SASL:
./configure --with-saslauthd=/var/lib/sasl2 --enable-cram --enable-digest --enable-plain --disable-anon --with-mysqlauxprop=/usr/local/lib

Now, if you're mysql.h is in a mysql subdirectory (like /usr/include/mysql) you'll need to edit plugins/mysqlauxprop.c and change #include <mysql.h> to #include <mysql/mysql.h>. Now you're ready to compile:
su -
make install

Now you have SASLv2 installed. You need to make one symlink from the place that SASL looks for plugins to the place it puts in plugins. The reason for this is in the documentation found in the doc/ directory, so if you're curious you can read about it there. Otherwise do:
ln -s /usr/lib/sasl2 /usr/local/lib/sasl2


If you previously had IMAPd 2.0.x installed you'll want to move the binaries out of the way and backup your old configs. Below I show the process for backing them up, but you may remove them if you wish. First I removed /usr/cyrus, this is just binaries and nothing here will be needed:
$ su - # cd /usr
# tar cf cyrus.2.0.16.tar cyrus
# rm -rf cyrus

Then I backup the old configs and remove them:
$ su - # cd /etc
# cp cyrus.conf cyrus.2.0.16.conf
# tar cf cyrus.2.0.16.etc.tar cyrus
# rm cyrus/*

Now download the Cyrus IMAPd 2.1.4 source from here. Extract it:
$ tar zxvf cyrus-imapd-2.1.4.tar.gz
$ cd cyrus-imapd-2.1.4

Next, if you installed OpenSSL from source, and plan to compile with SSL support (you are, aren't you?) you'll need to make a small edit. Edit Makefile.PL in perl/imap and perl/sieve/managesieve and change the line:

    'LIBS'      => ["$SASL_LIB -lssl -lcrypto"],

    'LIBS'      => ["-L/usr/local/ssl/lib $SASL_LIB -lssl -lcrypto"],
Then fix a bug in the configure script in accordance with this mailing list post (or you'll have problems configuring). This fix is edit configure and change:
LIBS="-lsasl2 $LIBS"

LIBS="-lsasl2 $cmu_saved_LDFLAGS $LIBS"

Now you're ready to configure. You'll need the --with-auth=unix switch, and if you installed OpenSSL from source in the default locations you'll need the --with-openssl=/usr/local/ssl switch. So:
$ ./configure --with-auth=unix --with-openssl=/usr/local/ssl
$ make depend
$ make all CFLAGS=-O

Before we install, we'll want to setup a user for cyrus:
# groupadd mail
# useradd -d /usr/cyrus -g mail cyrus
# passwd cyrus

And set a password for cyrus. When your done with that you can install with:
$ su -
# make install

Additionally, there's a directory in the source called tools with necessary tools that don't get installed anywhere. These are good to keep around so I like to do the following, as root, from within the source directory:
# mkdir /usr/cyrus/bin/tools
# cp tools/* /usr/cyrus/bin/tools
# chown -R cyrus:mail /usr/cyrus/bin/tools

Now you will want to setup syslog to do some logging for you. We will start by doing lots of logging incase you run into trouble - but we'll drop down the level later. Edit /etc/syslog.conf and add these two lines:

local6.debug	/var/log/imapd.log
auth.debug	/var/log/auth.log

NOTE: Many systems already log auth stuff to a file (often auth.log) - look in your syslog.conf, if you already have a line like this, don't add it again. Either way you'll need the imapd line. Now we'll want to touch those files:
# touch /var/log/imapd.log /var/log/auth.log

Up until now, we've been following the included documentation almost word-for-word, but here's where we'll change a few things.

You'll need one IP address for each domain you want to host. For our examples, we'll use for and for Obviously these need to be real IP addresses in your address space. So setup a virtual interface for your IP addresses (set these in your system configuration files so they will stay on reboot, for example, on redhat-like systems, you'll use the files in /etc/sysconfig/network-scripts/). Then edit your /etc/hosts file so it's something like:         localhost localhost.localdomain 

This works equally well if you use subdomains instead of domains, by the way (i.e. and Now, make a directory /etc/cyrus like this:
$ su -
# mkdir /etc/cyrus
# chown cyrus:mail /etc/cyrus

Then create config files for your domains (note these will be used instead of /etc/imapd.conf):
# touch /etc/cyrus/ /etc/cyrus/

Now it's time to setup your SQL tables. I used the same table names as web-cyradm since I want to patch web-cyradm to work with this setup. You may adjust to your liking. First connect to mysql as root and create a user and the database:
$ mysql mysql -u root -p
> INSERT INTO user (Host, User, Password, Select_priv, Insert_priv, Update_priv, D elete_priv, Create_priv, Drop_priv, Reload_priv, Shutdown_priv, Process_priv, Fi le_priv, Grant_priv, References_priv, Index_priv, Alter_priv) VALUES ('localhost', 'mail', PASSWORD('SECUREPASSWORD'), 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', ' N', 'N', 'N', 'N', 'N');
> INSERT INTO db (Host, Db, User, Select_priv, Insert_priv, Update_priv, Delete_pr iv, Create_priv, Drop_priv, Grant_priv, References_priv, Index_priv, Alter_priv) VALUES ('localhhost', 'mail', 'mail', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y' , 'Y', 'Y ');
> flush privileges;

Make sure to replace SECUREPASSWORD with a password. Despite the 'flush privileges,' I've found it necessary to reload mysql:
$ su -
# mysqladmin -u root -p reload

Now connect to the database as the user you just created and create the needed tables:
$ mysql mail -u mail -p
> CREATE TABLE accountuser (
username varchar(30) NOT NULL default '',
password varchar(30) binary NOT NULL default '',
prefix varchar(30) NOT NULL default '',
domain_name varchar(255) NOT NULL default ''
> CREATE TABLE adminuser (
username varchar(30) NOT NULL default '',
password varchar(30) binary NOT NULL default '',
type int(11) NOT NULL default '0',
SID varchar(255) NOT NULL default '',
home varchar(255) NOT NULL default '',
PRIMARY KEY (username)
> CREATE TABLE alias (
alias varchar(255) NOT NULL default '',
dest longtext,
username varchar(30) NOT NULL default '',
status int(11) NOT NULL default '1',
> CREATE TABLE domain (
domain_name varchar(255) NOT NULL default '',
prefix varchar(30) NOT NULL default '',
maxaccounts int(11) NOT NULL default '20',
quota int(10) NOT NULL default '20000',
PRIMARY KEY (domain_name)

Note here that accountuser is the only one required for your mail server to work. The rest will be for integration with web-cyradm (alias may be required at some point, I haven't tried to make aliases work with my current setup).

Next, edit each domain-specific configuration file giving it it's own configdirectory, partition-default, and servername. For example, domain1.conf would probably look like:
configdirectory: /var/imap/
partition-default: /var/spool/imap/
admins: cyrus root
altnamespace: yes
allowanonymouslogin: no
allowplaintext: yes
sasl_pwcheck_method: auxprop
sasl_mysql_user: mail
sasl_mysql_passwd: YourSecurePassword
sasl_mysql_hostnames: localhost
sasl_mysql_database: mail
sasl_mysql_statement: select password from accountuser where username = '%u' and domain_name = '%r'

Note here they the servername attribute will determine the realm automatically. So when someone contacts the imapd daemons using this config file, it will allow the user to use just 'username' and realm is '' Additionally configdirectory and partitiondirectory specify a unique directory for this specific domain eliminating any problems with similarly named mailboxes. The altnamespace attribute is optional. Turning it on enables folders to be created under the root (parallel to INBOX), while turning it off allows folders to be created only under INBOX.

The sasl_* options specify how the mysql auxprop plugin will contact the SQL database. By allowing the statement to be customized, the plugin allows a lot of power.

Make versions of this file for each domain you will be hosting.

Next we need to make our directories:
# cd /var
# mkdir imap
# chown cyrus:mail imap
# chmod 750 imap
# for domain in; do
> mkdir $domain;
> chown cyrus:mail $domain;
> chmod 750 $domain;
> done
# cd /var/spool
# mkdir imap
# chown cyrus:mail imap
# chmod 750 imap
# for domain in; do > mkdir $domain;
> chown cyrus:mail $domain;
> chmod 750 $domain;
> done

Now the directories are almost done, a script from the source (that we copied to /usr/cyrus/bin/tools) can do the rest! So the next step is:
# su - cyrus
$ for domain in; do
> /usr/cyrus/bin/tools/mkimap /etc/cyrus/${domain}.conf;
> done;
$ exit

Although undocumented the mkimap script takes an arguement of the configuration file to use as a reference. One more directory related thing ONLY IF YOU ARE ON LINUX (all other unices, skip on down): We need to set the user, quota, and partition directories to update synchronously. This is fairly simple:
# cd /var/imap
# for domain in; do
> cd /var/imap/$domain;
> chattr +S user quota user/* quota/*;
> chattr +S /var/spool/imap/$domain /var/spool/imap/$domain/*;
> cd ..;
> done;
# chattr +S /var/spool/mqueue

Next ensure all of the following are in /etc/services:

pop3      110/tcp
imap      143/tcp
imsp      406/tcp
acap      674/tcp
imaps     993/tcp
pop3s     995/tcp
kpop      1109/tcp
sieve     2000/tcp
lmtp      2003/tcp
fud       4201/udp
Don't forget those need to be TABS and not SPACES! Also be sure to remove any entries from /etc/inetd.conf or /etc/xinetd.d/ for imap, imaps, pop3, pop3s, kpop, lmtp, or sieve.

Now we need to tell Cyrus about our domains and where to find their custom configuration files. First you'll want a base configration file to start with, so from the source directory do:
# cp master/conf/normal.conf /etc/cyrus.conf

This is were we use the power of cyrus to support multiple domains. For each entry in the default cyrus.conf file, we will make a copy for each extra domain we want to host. We will also specify a the seperate configuration file for the services to use that we just created for each domain and we will also specify the interface to bind those services to. This is what the file will look like:

  # do not delete this entry!
  recoverdomain1	cmd="ctl_cyrusdb -C /etc/cyrus/ -r"
  recoverdomain2	cmd="ctl_cyrusdb -C /etc/cyrus/ -r"

  # this is only necessary if using idled for IMAP IDLE
  #idled                cmd="idled"

# UNIX sockets start with a slash and are put into /var/imap/socket
  # add or remove based on preferences

  imapdomain1	cmd="imapd -C /etc/cyrus/" listen="" prefork=1
  imapsdomain1	cmd="imapd -s -C /etc/cyrus/" listen="" prefork=1
  pop3dsdomain1	cmd="pop3d -s -C /etc/cyrus/" listen="" prefork=1
  lmtpunixdomain1	cmd="lmtpd -C /etc/cyrus/" listen="/var/imap/" prefork=0

  imapdomain2	cmd="imapd -C /etc/cyrus/" listen="" prefork=1
  imapsdomain2	cmd="imapd -s -C /etc/cyrus/" listen="" prefork=1
  pop3sdomain2	cmd="pop3d -s -C /etc/cyrus/" listen="" prefork=1
  lmtpunixdomain2	cmd="lmtpd -C /etc/cyrus/" listen="/var/imap/" prefork=0

  #UNUSED SERVICES - keep around for reference
  # pop3                cmd="pop3d" listen="pop3" prefork=0
  # pop3s       cmd="pop3d -s" listen="pop3s" prefork=0
  # imap                cmd="imapd" listen="imap" prefork=0
  # imaps       cmd="imapd -s" listen="imaps" prefork=0
  # sieve       cmd="timsieved" listen="sieve" prefork=0
  # lmtp                cmd="lmtpd" listen="lmtp" prefork=0
  # lmtpunix    cmd="lmtpd" listen="/var/imap/socket/lmtp" prefork=0

  # this is only necessary if using notifications
  # notify      cmd="notifyd" listen="/var/imap/socket/notify" proto="udp" prefork=1

  # this is required
  checkpointdomain1	cmd="ctl_cyrusdb -c -C /etc/cyrus/" period=30
  checkpointdomain2	cmd="ctl_cyrusdb -c -C /etc/cyrus/" period=30

  # this is only necessary if using duplicate delivery suppression
  delprunedomain1	cmd="ctl_deliver -E 3 -C /etc/cyrus/" period=1440
  delprunedomain2	cmd="ctl_deliver -E 3 -C /etc/cyrus/" period=1440

  # this is only necessary if caching TLS sessions
  tlsprunedomain1	cmd="tls_prune -C /etc/cyrus/" period=1440
  tlsprunedomain2	cmd="tls_prune -C /etc/cyrus/" period=1440
NOTE: In 2.1.4 only alpha characters are support for services names (i.e. you can use numbers, dashes, dots, spaces, etc.). I've used numbers here for simplicity, but if you want to use non-alpha characters you'll have to wait until 2.1.5, I believe Ken M. has changed this for the next version.

Be sure to change the configuration file name as well as the 'listen' attributes. Now when we start Cyrus a seperate server on each IP address using seperate configurations and seperate directories will be launched. This allows for extraordinary flexibility.

Next, we will add some test users as well as some administrative users to the SQL database:
$ mysql mail -u mail -p
> INSERT INTO accountuser VALUES('phil','MyPasswd1','','');
> INSERT INTO accountuser VALUES('phil','MyPasswd2','','');
> INSERT INTO accountuser VALUES('cyrus','UberSecure1','','');
> INSERT INTO accountuser VALUES('cyrus','UberSecure2','','');

Now we can startup the Cyrus master:
$ su -
# /usr/cyrus/bin/master &

Now we will need to create mailboxes for the users we've created in the mailbox:
$ su - cyrus
$ cyradm> cm user.phil> quit
$ curadm> cm user.phil> quit
$ exit

We should be set to test now! We'll use the imtest utility to test authentication:
$ imtest -a phil -u phil -p imap
$ imtest -a phil -u phil -p imap
If that works (it will tell you if authentication works or fails), then all is working! Note that you may want to change your syslog.conf file and turn the debugging level down on local6 once you have this working. Don't forget to enable SSL, and configure your MTA, desribed below.

Extra Security

I recommend you use a setting such as:
sasl_minimum_layer: 128

to prevent people from using plain, or weak encryption. If a client claims it can only do less, and you do not define a minimum layer, Cyrus will comply. With a minimum layer, Cyrus will not authenticate anyone who cannot do atleast that security level.

Making Cyrus Start On Boot

Unfortunately, Cyrus doesn't come with a SYSV init script. But that's ok, we can make one easily enough. Create a file called /etc/init.d/cyrus, and make it look something like this:


# This script starts, stops, or restarts the
# Cyrus master.
# It was written by Phil Dibowitz

case "$1" in
                echo -n "Starting Cyrus IMAPd..."
                /usr/cyrus/bin/master &
                echo $! > /var/run/
                echo "done"
                echo -n "Stopping Cyrus IMAPd..."
                if [ -e /var/run/ ] ; then
                        kill `cat /var/run/`
                        rm /var/run/
                        echo "done"
                        echo "Sorry, can't find PID file, is it running?"
                $0 stop
                sleep 2
                $0 start
                echo "Usage: $0 {start|stop|restart}"

NOTE WELL: If you use postfix stop/restart will KILL postfix! You either need to modify this script, OR rename the postfix 'master' to 'pf_master' (or something like that), and change postfix's initscript, or some combination thereof.

So now you have an init script, lets make it active. Assuming your default runleve is 3, do:
cd /etc/rc3.d
ln -s ../init.d/cyrus S95cyrus

Adjust the number to taste. If you are on Solaris you should probably make a similar link to K05cyrus so the server stops when you leave runlevel 3.

Coming Soon

A hacked version of web-cyradm to do atleast some basic admin of this setup.

Adding SSL to Cyrus

I've put this on a different page.

Configuring SMTP

I've also moved this to it's own page.


Find troubleshooting info here.

Last Updated: 06/26/02

This page is © Phil Dibowitz 2001 - 2004