We wish to build a mail server where the users do not each have entries in /etc/passwd. Not only does this keep the password file small, but it should make our system more secure. Also, some Unixes have a limit of 65,535 entries in /etc/passwd.
All our mail directories will be owned by one user (exim). The instructions for where to deliver mail will be held in a database file, which says how to deliver mail for a given incoming address - either to store it in a Maildir, or to forward it elsewhere.
At this stage, we will also introduce quotas. Quotas ensure that users cannot use more than a certain amount of disk space - those users who have set 'leave mail on server' will eventually have to download old mail to make space for new messages. We will send them a warning to let them know.
Actually, we require three database files. One is used to configure local_domains; keeping this in a database rather than directly in the configure file lets us scale to thousands of domains efficiently. The second is used to map actual E-mail addresses to the physical directory location on the disk. The third maps the physical directory to a quota value, although this can be set as a global default and overridden for "special cases" only.
This fully "table-driven" approach gives a lot of flexibility, if you are happy to maintain these database files!
The mail spool is easy. Let's choose /u/mail (our /u partition has lots of space), and change its ownership so that only exim can access it. Exim will create subdirectories under here when it needs to.
# mkdir /u/mail # chown exim:exim /u/mail # chmod 700 /u/mail
Now we need to create the three database files.
# cd /usr/exim # vi vdomains *.linnet.org # vi valiases *@fred.linnet.org: /u/mail/00/fred/ *@jim.linnet.org: /u/mail/01/jim/ # vi mquota /u/mail/00/fred/: 25000000 # cd /usr/exim # bin/exim_dbmbuild vdomains vdomains.db 1 entry written # bin/exim_dbmbuild valiases valiases.db 2 entries written # bin/exim_dbmbuild mquota mquota.db 1 entry written
(In a production environment you would write a script to do the above commands). Check that the appropriate files have been created:
# ls /usr/exim bin mquota valiases vdomains configure mquota.db valiases.db vdomains.db
Copy the new configure file to /usr/exim/configure-mx, then test it is doing database lookups correctly, as well as still being able to deliver remotely.
# exim -C /usr/exim/configure-mx -bt root@fred.linnet.org root@fred.linnet.org -> /u/mail/00/fred/ # exim -C /usr/exim/configure-mx -bt root@jim.linnet.org root@jim.linnet.org -> /u/mail/01/jim/ # exim -C /usr/exim/configure-mx -bt nobody@nsrc.org nobody@nsrc.org deliver to nobody@nsrc.org router = lookuphost, transport = remote_smtp host psg.com [147.28.0.62] MX=10
Now deliver a message. We will turn on a little debugging (-d1) so you can see some of what is happening.
# exim -C /usr/exim/configure-mx -v -d1 root@fred.linnet.org Exim version 3.22 debug level 1 uid=0 gid=0 probably Berkeley DB version 1.8x (native mode) Subject: test test message [Ctrl-D] LOG: 0 MAIN <= root@noc.ws.afnog.org U=root P=local S=329 bash# delivering message 14wB9p-000CJJ-00 LOG: 0 MAIN => /u/mail/00/fred/ <root@fred.linnet.org> D=valiases T=maildir_internal LOG: 0 MAIN Completed # ls -lR /u/mail/00/fred total 3 drwx------ 2 exim wheel 512 May 5 23:08 cur drwx------ 2 exim wheel 512 May 5 23:08 new drwx------ 2 exim wheel 512 May 5 23:08 tmp /u/mail/00/fred/cur: /u/mail/00/fred/new: total 1 -rw------- 1 exim exim 400 May 5 23:08 989104129.47327.noc.ws.afnog.org,S=400 /u/mail/00/fred/tmp:
You can see that the Maildir and its subdirectories have been created, and the message has been delivered; use 'cat' to read it. Try using higher debug levels (-d2...-d9) for more information about exim internal operation.
Database lookups have been introduced in this configuration.
local_domains = partial2-dbm;/usr/exim/vdomains.db : localhost : @
The domain part of the address is checked by doing a dbm lookup in vdomains.db, or if it is not there, see if it matches the string "localhost" or the hostname of this mailserver itself. "partial2-dbm" is a dbm lookup which allows partial matches on domains. For example, when looking up "mx-1.mail.example.com" the following lookups are attempted:
mx-1.mail.example.com *.mx-1.mail.example.com *.mail.example.com *.example.com
The '2' means that at least two domain portions must remain, so that '*.com' does not match for example.
Another dbm lookup is done in the valiases director, this time using the aliasfile's lookup facility (search_type and file). The lookup type here is dbm*@, meaning "lookup in the dbm file, and if there is no match, replace everything to the left of @ with *".
valiases: driver = aliasfile search_type = dbm*@ file = /usr/exim/valiases.db include_domain qualify_preserve_domain # For alias entries /which/look/like/this/, we deliver directly to a # Maildir at this location. Hide this path in any bounce messages though. directory_transport = maildir_internal hide_child_in_errmsg
Finally, there is a database lookup in the transport which drops the message into a maildir. We set the value of the 'quota' attribute using an expansion string which uses the ${lookup} operator, whose syntax for this type of lookup is ${lookup{key}type{file}{matchval}{failval}}
maildir_internal: ... maildir_tag = ,S=$message_size quota_size_regex = ,S=(\d+) # Quota handling quota = ${lookup{$address_file}dbm{/usr/exim/mquota.db}{$value}{DEFQUOTA}}
This last example gives an idea of how flexible Exim is, because there are many places in the configuration where expansion strings like this can be used to look up information from elsewhere.
We have also introduced a trick for efficient quota handling.
'maildir_tag' makes Exim encode the size of the file within the
filename, as ,S=nnn, and 'quota_size_regex' tells Exim how to
retrieve it from the filename. This saves Exim a lot of work when calculating
quotas; it just has to do a readdir()
to list the directory
contents, without having to do a stat()
on every single file to
get its size. This only works because the users do not have accounts on the
box (otherwise, they would be able to rename the files to get round the quota
limits)