It was a simple exercise. Set up the venerable sendmail server with the popular IMAP server Dovecot, using virtual domains.

  1. Dovecot configuration
  2. Sendmail setup (including saslauthd)
  3. Configuring Squirrelmail
  4. An odd problem: Mail from system to virtual accounts
  5. Configuring Mozilla Thunderbird
  6. Some testing tips
    Addendum to Section 4

And it turned into an almost nightmarish obstacle course. But finally, it's all up and running. Let me walk you through the steps. Note that all of these steps are necessary. Leave any one of them out, and things will just not work. To figure out why... let's just say that on more than one occasion, I actually resorted to using the strace utility to find out what the devil is going on. Google of course helped a great deal, but it didn't have all the answers.

I am assuming that Sendmail and Dovecot are installed on the target system. The target in my case is a CentOS 7 server; configuration file locations are set up accordingly.

1. Dovecot configuration

So let us begin with configuring Dovecot.

Our goal is to let Dovecot accept mail both for system users (who have UNIX accounts on the host) and virtual users, who may be using any one of several domains.

To begin, make sure that in /etc/dovecot/conf.d/10-auth.conf, the following two lines are uncommented:

!include auth-system.conf.ext
!include auth-passwdfile.conf.ext

These two lines allow Dovecot to recognize both system users and users whose identities are stored on a Dovecot authentication file.

Next, in /etc/dovecot/conf.d/auth-passwdfile.conf.ext, make sure that the args entry in the passdb section reads

args = scheme=SSHA256 username_format=%u /etc/dovecot/users

This line ensures that a strong encryption method is used for storing Dovecot passwords. Furthermore, still in auth-passwdfile.conf.ext in the userdb section, make sure to include

override_fields = mail=mbox:~/mail

Without this, dovecot won't be able to figure out mailbox locations.

Next, in /etc/dovecot/conf.d/10-master.conf, make sure that in the unix_listener auth-userdb section, the following two lines are present:

user = vmail
group = vmail

These lines are quite essential to ensure that Dovecot's local delivery agent (which will be invoked by Sendmail) can talk to Dovecot itself.

We now need to specify where mail is delivered. In /etc/dovecot/conf.d/10-mail.conf, make sure that you have the following line:

mail_home = /home/vmail/%d/%n

Also make sure that you do not have a mail_location line specified. This lets Dovecot figure things out (and, in particular, ensure that system users see their system mailbox, whereas virtual users have their inboxes under their user folders.)

Additionally, still in /etc/dovecot/conf.d/10-mail.conf, you need to specify the UID and GID of the virtual mail account:

mail_uid = 1002
mail_gid = 1002

The numeric values should correspond to the numeric UID and GID of the vmail account and group on your system.

Finally, we need to configure SSL. I am assuming that you are using a Let's Encrypt SSL certificate, at its default location. The /etc/dovecot/conf.d/10-ssl.conf file must then contain the following entries:

ssl_cert = </etc/letsencrypt/live/your.domain.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/your.domain.com/privkey.pem

This completes the configuration of Dovecot. We still need to add users, though. Virtual users will be listed in /etc/dovecot/users, in the following format:

user@domain.com:{SSHA256}TL.....GO::::::

The actual password is generated using the command doveadm pw -s SSHA256, and must be copied-and-pasted into the users file. (There may be easier ways to do this, but I have not researched that.)

One last thing is to make sure that on systems with SELinux running, Dovecot does not run into a permissions problem. To fix this, create the file dovecot-lda.te with the following content:

module dovecot-lda 1.0;

require {
type dovecot_deliver_t;
class capability { setgid setuid };
}

#============= dovecot_deliver_t ==============
allow dovecot_deliver_t self:capability { setgid setuid };

(This file can also be automatically created using the SELinux audit log and the audit2allow utility.) To process and install this file, run the following commands:

checkmodule -M -m -o dovecot-lda.mod dovecot-lda.te
semodule_package -o dovecot-lda.pp -m dovecot-lda.mod
semodule -i dovecot-lda.pp

We're really done with Dovecot now, but unfortunately, we cannot test it just yet. To test the configuration, we need a functioning SMTP server so that you can send mail to these mailboxes, and we also need an IMAP client. Let us continue with Sendmail.

2. Sendmail setup (including saslauthd)

There are several things that we wish to accomplish when setting up Sendmail. In no particular order, these are the following:

  1. Make sure that Sendmail accepts e-mail for all virtual domains;
  2. Make Sendmail use Dovecot as its delivery agent for virtual domains;
  3. Allow Sendmail to authenticate both system and virtual users for outgoing e-mail;
  4. Make Sendmail offer encrypted connections for authenticated users who send outgoing e-mail.

A prerequisite for item 3 is the presence of the SASL authorization demon (saslauthd) with appropriate modules. On CentOS 7, we need

yum install cyrus-sasl
yum install cyrus-sasl-plain
yum install cyrus-sasl-md5

The configuration file for the demon is /etc/sysconfig/saslauthd. Make sure that this file contains the following two lines:

MECH=rimap
FLAGS="-O localhost -r"

The first of these two lines specifies that the demon will authenticate by connecting to an IMAP server. The address of the server is given by the -O option. The -r option, in turn, ensures that for virtual domain users, the full username (with domain name) is authenticated.

Next, we need to make sure that Sendmail does use the SASL demon for authentication, communicates via SSL/TLS over port 465, and uses the appropriate security certificates. For this, we need to add the following lines to sendmail.mc:

define(`confAUTH_MECHANISMS', `DIGEST-MD5 CRAM-MD5 LOGIN PLAIN')dnl
TRUST_AUTH_MECH(`DIGEST-MD5 CRAM-MD5 LOGIN PLAIN')dnl
define(`confCACERT_PATH', `/etc/letsencrypt/live/your.domain.com')dnl
define(`confCACERT', `/etc/letsencrypt/live/your.domain.com/fullchain.pem')dnl
define(`confSERVER_CERT', `/etc/letsencrypt/live/your.domain.com/fullchain.pem')dnl
define(`confSERVER_KEY', `/etc/letsencrypt/live/your.domain.com/privkey.pem')dnl
define(`confCLIENT_CERT', `/etc/letsencrypt/live/your.domain.com/fullchain.pem')dnl
define(`confCLIENT_KEY', `/etc/letsencrypt/live/your.domain.com/privkey.pem')dnl
define(`confAUTH_OPTIONS', `A p y')dnl DAEMON_OPTIONS(`Port=smtp')dnl DAEMON_OPTIONS(`Port=smtps, Name=TLSMTA, M=s')dnl

Do make sure that there aren't already conflicting lines defined in sendmail.mc; remove or comment them out if necessary.

Additional lines are needed in sendmail.mc to ensure that Dovecot's local delivery agent (LDA) is invoked for, well, local delivery:

VIRTUSER_DOMAIN_FILE(`/etc/mail/virtuserdomains')
MAILER(dovecot)dnl
MAILER(smtp)dnl

Note that we do not need the local and procmail mailers. Comment them out, if necessary.

Actual virtual domains must be included in the (newly created, if necessary) file /etc/mail/virtuserdomains. These domains must also be included in the file /etc/mail/mailertable, in the following format:

your.domain.com dovecot:dovecot

At the same time, make sure that virtual domains are not included in /etc/mail/local-host-names or /etc/mail/relay-domains.

Finally, we need to define the dovecot mailer. Create the file /usr/share/sendmail-cf/mailer/dovecot.m4 with the following content:

######################*****##############
### DOVECOT Mailer specification ###
##################*****##################
Mdovecot, P=/usr/libexec/dovecot/dovecot-lda, F=DFMPhnul59,
S=EnvFromSMTP/HdrFromSMTP, R=EnvToSMTP/HdrFromSMTP,
T=DNS/RFC822/X-Unix,
U=vmail:vmail,
A=/usr/libexec/dovecot/dovecot-lda -d $u

(Yes, those leading spaces, or tabs, are essential in the lines following the Mdovecot line.)

Last but not least, you may want to make sure that certain e-mail addresses in virtual domains are redirected to corresponding system mailboxes. E.g., on one of the systems, I have the following line in /etc/mail/virtusertable:

vttoth@your.domain.com vttoth

This ensures that e-mail addressed to me in the virtual domain is, in fact, delivered to my system mailbox instead.

With these changes, the configuration of sendmail is complete. The sendmail.mc file can be recompiled by running make in /etc/mail. Afterwards, it is time to enable and start all the relevant services:

systemctl enable dovecot saslauthd sendmail
systemctl restart dovecot saslauthd sendmail

To test if the configuration works, it is necessary to set up an IMAP client. The easiest is to use Squirrelmail, the venerable, no-frills webmail implementation.

3. Configuring Squirrelmail

Assuming that your Apache Web server is up and running over HTTPS, with properly installed security certificates, Squirrelmail should pretty much work "out of the box" after installation. However, in order for it to function properly in the presence of SELinux, the following two commands may be needed (NB: The first of these may be sufficient):

setsebool -P httpd_can_network_connect 1
setsebool -P httpd_can_sendmail 1

Now point your browser at https://your.domain.com/webmail . If you did everything right, you are now able to send and receive e-mail. If you aren't... well, then you will learn the hard way how I spent the last several days.

4. An odd problem: Mail from system to virtual accounts

When everything looked set, one of the last things I tried was to send an e-mail from a system account to a virtual user. Much to my disappointment, the send failed. I then spent many hours trying to figure out what the dickens was happening.

To make a long story short, when the sender is a local system account, Sendmail, it appears, insists on running the local delivery agent using that user's credentials. By default, then, the LDA is unable to get account information from dovecot itself. Changing permissions of the Dovecot authorization server socket works, but it leads to a new problem: The LDA wants to change its effective user ID and fails. Making the LDA suid doesn't work either. In short, it is a tangled mess.

The way out of this tangled mess involves the use of the sudo command. First, add a file to /etc/sudoers.d with the following content:

ALL  ALL=(vmail,vmail)  NOPASSWD:  /usr/libexec/dovecot/dovecot-lda

This permits anyone to run the Dovecot delivery agent using the vmail credentials. (NB: Check the security implications of this in your system context. In my case, this solution is acceptable. In your case, it might not be.)

Second, create an executable, /usr/local/bin/dovelda-proxy, with the content

#!/bin/sh

/usr/bin/sudo -u vmail /usr/libexec/dovecot/dovecot-lda $*

Finally, modify dovecot.m4 to contain:

Mdovecot, P=/usr/local/bin/dovelda-proxy, F=DFMPohnul59,
S=EnvFromSMTP/HdrFromSMTP, R=EnvToSMTP/HdrFromSMTP,
T=DNS/RFC822/X-Unix,
U=vmail:vmail,
A=/usr/local/bin/dovelda-proxy -d $u

Then recompile sendmail.cf and restart the Sendmail server.

One more caveat: If you are using SELinux, this will trigger a truckload of warnings (including some that are normally suppressed; use semodule -DB to disable dontaudit rules.) I presume you know how to use audit2allow to generate an SELinux rules file. Again, use it at your own risk, after properly evaluating the security implications.

5. Configuring Mozilla Thunderbird

Once everything is up and running and Squirrelmail works both for sending and receiving e-mail, the final step remains: Configuring an external IMAP-capable program to connect to the server and send and receive e-mail.

I chose Mozilla Thunderbird for this purpose, as it is an excellent cross-platform mail client and also, its setup does not interfere with the mail software that I normally use for my regular e-mail. (That said, I am actually tempted to switch to Thunderbird. It really is that good.)

Before an external agent can connect to the server, however, it is necessary to open some ports:

firewall-cmd --permanent --add-service=imaps
firewall-cmd --permanent --add-service=smtps
firewall-cmd --reload

(If not already open, the SMTP port must also be opened to the world.)

Actual settings for the mail client for a virtual domain user are as follows. First, for incoming e-mail:

Server type: IMAP
Server name: your.server.com
User name: user@your.domain.com
Port number: 993
Security: SSL/TLS
Authentication: Normal (plaintext) password

For outgoing e-mail, the settings are:

Server name: your.server.com
User name: user@your.domain.com
Port number: 465
Security: SSL/TLS
Authentication: Normal (plaintext) password

Note that in both cases, the username includes the fully qualified domain name. The server name, in turn, must be the name for which the security certificate is registered. It need not be the same as the domain name used for the virtual e-mail domain.

With these settings, you should be able to access your mailbox and send and receive e-mail.

6. Some testing tips

The instructions on this page are a result of several days spent experimenting and trying to get things up and running. During this process, many things went wrong and I used a number of techniques to try and figure out what the devil is going on. Here are a few things worth trying.

First, /var/log/maillog is your friend. Often, the actual cause of the problem is recorded there. Also don't forget /var/log/messages, as certain errors are logged here, not the mail log.

For testing, it is very useful to have another box handy, which is configured for outgoing e-mail, and from which you can easily send messages from the command line. Trust me, you will be sending dozens, if not hundreds, of test e-mails if something goes wrong.

Additional logging for Dovecot can be enabled in /etc/dovecot/conf.d/10-logging.conf. E.g., you may decide to add the lines

log_path=/tmp/dovecot.log
auth_verbose = yes
auth_debug = yes
mail_debug = yes

If you choose to send the log to a file, keep in mind that invoking things from the command line vs. invoking them from Sendmail means different SELinux security contexts. The easiest way to deal with this is to delete the log file after every invocation.

To test if Dovecot can correctly authenticate a user, you can connect to the Dovecot domain socket using the nc command:

nc -U /var/run/dovecot/auth-userdb

Once connected, first issue a VERSION command, followed by one or more USER commands. E.g.,

VERSION↦1↦0
USER↦1↦user@your.domain.com service=lda

Note that the right-arrow symbol (↦) is used to represent a TAB.

To manually test the Dovecot LDA to see if it is working, you may use something like

cat test-message | /usr/libexec/dovecot/dovecot-lda -d user@your.domain.com

where the file test-message may contain something like

Subject: test

This is a test message.

To test if SASL is working correctly, try

testsaslauthd -u user@your.domain.com -p password

To verify that Sendmail can properly authenticate users through SASL, you can do the following. First, encode the username and password as follows:

echo -ne "\0user@your.domain.com\0password" | base64

Next, temporarily enable plaintext authentication over unencrypted connections by changing one line in sendmail.cf and then restarting Sendmail:

O AuthOptions=A

After this, you can connect to port 25 on localhost and then issue the commands

EHLO TEST
AUTH PLAIN base64-encoded-credentials

Don't forget to revert your changes in sendmail.cf and restart Sendmail afterwards.

Finally, as a last resort, you may use the strace command to log all system calls when things do not work the way they're supposed to. If you are doing this, I presume you know what you are doing.

Addendum (mainly) to section 4 (Updated March 11, 2022)

On March 7, 2022, I received a note from Dan Astoorian from the University of Toronto, who found my steps outlined here useful, but also discovered an alternate method to deal with the issue of system-to-virtual-account messages. Here is what he wrote (slightly abridged and edited):

I encountered the same issue you described in section 4, where the LDA is run as a local user rather than the user defined in the U= section when the mail is sent from a local account.

After further investigation, I found that adding the S flag to the flags in the mailer definition (rather than employing your workaround using sudo) resolved the problem. As per https://sendmail.org/~ca/email/doc8.12/op-sh-5.html:


S
Don't reset the userid before calling the mailer. This would be used in a secure environment where sendmail ran as root. This could be used to avoid forged addresses. If the U= field is also specified, this flag causes the effective user id to be set to that user.


I found an alternative method of configuring sendmail's delivery to dovecot by delivering to its LMTP socket instead of invoking the delivery agent binary; I'm not sure what the pros and cons of each method are, but intuitively, it seems to me that a socket connection should be more efficient than starting up a separate process, all else being equal.

To configure LMTP delivery, the sendmail mailer can be defined as follows:

Mdovecot, P=[IPC],
F=lsDFM:/|@qPSXnz9um,
S=EnvFromSMTP/HdrFromL, R=EnvToL/HdrToL,
T=DNS/RFC822/X-Unix,
A=FILE /var/run/dovecot/lmtp

The default location of the LMTP socket (/var/run/dovecot/lmtp) may vary according to dovecot's compiled-in defaults and/or configuration (e.g., the value of base_dir=).

In some configurations it might also be necessary to add lmtp to the protocols= line in the dovecot.conf file, but I think it's normally enabled by default.

The mailer flags I defined in F= are taken from the example posted by a user at https://www.dovecot.org/list/dovecot/2016-January/102950.html, but with the m flag added (since LMTP can accept multiple messages in a transaction). The result is similar to the flags defined the "local" mailer in my distribution's default configuration, except:

  • the m, X and z flags are added (to configure the mailer to use LMTP, permit multiple messages per transaction, and use the "hidden dot" algorithm);
  • the w flag is removed (since the recipient does not need to have a local account on the machine);
  • the f flag is removed (I don't think it's relevant);
  • the h flag ("upper case should be preserved in host names") is removed;
  • the u flag ("upper case should be preserved in user names") is added;
  • the S= and R= settings are also different, in that:
    • your example used
      S=EnvFromSMTP/HdrFromSMTP, R=EnvToSMTP/HdrFromSMTP;
    • the local mailer uses
      S=EnvFromL/HdrFromL, R=EnvToL/HdrToL
    • the example I adapted from the mailing list uses
      S=EnvFromSMTP/HdrFromL, R=EnvToL/HdrToL
  • the A and 5 flags are removed (using ruleset 5 improperly redirects addresses of the form user+detail@your.domain.com to the local mailer instead of the dovecot mailer).

I'm not sure whether the changes to the h or u flags are necessary or desirable, and I'm still working out the ramifications of the sender and recipient rulesets (S= and R=).