Skip to main content

Dovecot

Dovecot handles storage of mail as well as providing IMAP access to emails on the kasad.com mail server.

Installation

Dovecot is broken into several Debian packages. We need dovecot-imapd and dovecot-sieve, which both require dovecot-core:

# apt install dovecot-{core,imapd,sieve}

Enabling the service

The dovecot-core package comes with a systemd service for Dovecot which we will enable:

# systemctl enable dovecot.service

Note: Enabling dovecot.socket is not enough, as that will not trigger when Postfix tries to use Dovecot for SASL, leading to broken authentication in Postfix until the IMAP server is accessed.

Configuration

Typically, Dovecot is configured using multiple drop-in configuration files in /etc/dovecot/conf.d. However, for smaller setups like ours, it's much easier to put all of the configuration in /etc/dovecot/dovecot.conf and ignore the drop-in files.

Below are the contents of dovecot.conf. The comments in the file explain briefly what each section does, but they are documented in more detail in the following sections.

# /etc/dovecot/dovecot.conf

# Note that in the dovecot conf, you can use:
# %u for username
# %n for the name in name@domain.tld
# %d for the domain
# %h the user's home directory

# SSL should be set to required.
ssl = required
ssl_cert = </etc/letsencrypt/live/kasad.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/kasad.com/privkey.pem

# Plaintext login. This is safe and easy thanks to SSL.
auth_mechanisms = plain login

protocols = $protocols imap sieve

# Search for valid users in /etc/passwd
userdb {
	driver = passwd
}
#Fallback: Use plain old PAM to find user passwords
passdb {
	driver = pam
}

# Our mail for each user will be in ~/.mail, and the inbox will be ~/.mail/Inbox
# The LAYOUT option is also important because otherwise, the boxes will be `.Sent` instead of `Sent`.
mail_location = maildir:~/.mail:INBOX=~/.mail/Inbox:LAYOUT=fs
namespace inbox {
	inbox = yes

	mailbox Drafts {
		special_use = \Drafts
		auto = subscribe
	}

	mailbox Junk {
		special_use = \Junk
		auto = subscribe
		autoexpunge = 30d
	}

	mailbox Spam {
		special_use = \Junk
		auto = no
		autoexpunge = 30d
	}

	mailbox Sent {
		special_use = \Sent
		auto = subscribe
	}

	mailbox Trash {
		special_use = \Trash
		auto = subscribe
	}

	mailbox Archive {
		special_use = \Archive
	}

	mailbox Archives {
		special_use = \Archive
	}

}

# Here we let Postfix use Dovecot's authetication system.
service auth {
 	unix_listener /var/spool/postfix/private/auth {
		mode = 0660
		user = postfix
		group = postfix
	}
}

mail_plugins = $mail_plugins zlib

plugin {
	zlib_save = zstd
}

protocol lda {
	mail_plugins = $mail_plugins sieve
}

protocol lmtp {
	mail_plugins = $mail_plugins sieve
}

plugin {
	sieve = file:~/.sieve;active=~/.dovecot.sieve
	sieve_default = /etc/dovecot/sieve/default.sieve
	sieve_global = file:/var/lib/dovecot/sieve
	recipient_delimiter = +
}

TLS settings

We want to enable and enforce TLS for connections. Unlike Postfix, Dovecot's ports are only accessed by our own users, so we don't need to worry about compatibility issues with other mail servers.

ssl = required
ssl_cert = </etc/letsencrypt/live/kasad.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/kasad.com/privkey.pem

SASL authentication mechanisms

SASL supports multiple methods, called mechanisms, for the user to transmit their password to the server. Since all connections to Dovecot will use TLS, we can enable plaintext mechanisms. They also happen to be the only ones supported by the PAM authentication backend.

auth_mechanisms = plain login
disable_plaintext_auth = yes

The second option above disables plaintext mechanisms for unencrypted connections. This doesn't matter since we require TLS, but it's a nice security fallback if the TLS settings are accidentally changed.

Protocols

We need to tell Dovecot which protocols we want to enable. In our case, this is IMAP and Sieve. Sieve is a scripting language which allows users to create rules to automatically filter or categorize incoming mail.

protocols = imap sieve

User & password databases

We want to use standard UNIX users as the user database for our mail system. We can easily do this in Dovecot. Authentication is broken down into two databases, a user database and a password database. The password database handles authentication of the user when they log in. The user database provides information on the user's UID, GID, and home directory after they've authenticated.

PAM password database

We want to use PAM as the password database. It is possible to use the /etc/shadow file, but PAM provides more customization options.

passdb {
	driver = pam
}

We also want to restrict access to users in the mail group, as we don't want all UNIX users to be able to receive/send mail. We can accomplish this by adding the following line at the end of the auth stack in the /etc/pam.d/dovecot file:

auth	required	pam_succeed_if.so	user ingroup mail

This will cause authentication attempts for users not in the mail group to fail, the same way as if they provided an invalid password.

Passwd file user database

After a user has authenticated, we can look up their UID, GID, and home directory in the /etc/passwd file:

userdb {
	driver = passwd
}

Mail storage location

We need to tell Dovecot where we want it to store mail. We'll use the Maildir format for storing emails, as it's the easiest to work with:

mail_location = maildir:~/.mail:INBOX=~/.mail/Inbox:LAYOUT=fs
  • LAYOUT=fs: Use heirarchical directories for IMAP folders
  • INBOX=~/.mail/Inbox: Creates a distinct Inbox folder rather than putting the inbox emails in the Maildir root

Inbox namespace

Dovecot supports IMAP namespaces. So far, we only define one namespace, which is the default inbox namespace. This namespace also contains default mailbox definitions.

namespace inbox {
	inbox = yes

	mailbox Drafts {
		special_use = \Drafts
		auto = subscribe
	}

	mailbox Junk {
		special_use = \Junk
		auto = subscribe
		autoexpunge = 30d
	}

	mailbox Sent {
		special_use = \Sent
		auto = subscribe
	}

	mailbox Trash {
		special_use = \Trash
		auto = subscribe
	}

	mailbox Archives {
		special_use = \Archive
	}
}

The inbox = true setting for the namespace tells Dovecot that this is the namespace which contains the inbox folder where new mail should be delivered to.

The mailbox definitions tell Dovecot about some default mailboxes. Users can create additional mailboxes on top of these. Each mailbox has some options which are explained below.

The auto option controls whether Dovecot will automatically create the mailbox folder. The no value means it will not. The create value means it will automaticaaly create the mailbox, and the subscribe value means it will both create the mailbox and subscribe the user to it.

The special_use option assigns an IMAP special use flag to the mailbox. This essentially tells IMAP clients which mailboxes (folders) are used for special purposes, like storing drafts or spam email.

The autoexpunge option will automatically expunge, or permanently delete, messages in the specified mailbox when they are older than the time given by the value. For instance, the Junk folder is set to automatically delete emails inside it that are older than one month.

Postfix SASL connector

We have configured Postfix to use Dovecot as its SASL provider. For this to work, we need to configure Dovecot to provide a SASL listener socket to Postfix:

service auth {
	unix_listener /var/spool/postfix/private/auth {
    	mode  = 0660
        user  = postfix
        group = postfix
    }
}

The path for the socket must be /var/spool/postfix/$smtpd_sasl_path as specified in Postfix's main.cf configuration file.

Compressed mail storage

We want to enable compressed mail storage, as it'll help us save some disk space. To do this, we use the zlib plugin:

mail_plugins = $mail_plugins zlib

We can then configure the zlib plugin:

plugin {
	zlib_save = zstd
}

This option configures Dovecot to compress new emails with the Zstandard compression algorithm.

Sieve filtering

We want to enable users to create filters in the form of Sieve scripts. To do this, we will enable the sieve plugin for the LDA and LMTP protocols in Dovecot:

protocol lda {
	mail_plugins = $mail_plugins sieve
}

protocol lmtp {
	mail_plugins = $mail_plugins sieve
}

Make sure to place these two blocks after setting the mail_plugins option globally. If you modify mail_plugins later in the file, the protocol block will not be updated.

We also need to configure the Sieve plugin:

plugin {
	sieve = file:~/.sieve;active=~/.dovecot.sieve
	sieve_default = /etc/dovecot/sieve/default.sieve
	sieve_global = file:/var/lib/dovecot/sieve
	recipient_delimiter = +
}

The first option, sieve defines where sieve scripts will be stored. We set it to store Sieve scripts in ~/.sieve. This is also where scripts included in other scripts will be loaded from. Only one Sieve script can be active at a time. This active script is symlinked to from ~/.dovecot.sieve.

The second option, sieve_default, specifies the default Sieve script to use if the user has not provided their own.

The third option, sieve_global, specifies the directory where global scripts will be stored. This also controls where scripts that are included in other global Sieve scripts will be loaded from.

Finally, we set the recipient_delimiter character to match what we've configured in Postfix, which is the + plus character.

Default Sieve script

We've defined the default Sieve script location as /etc/dovecot/sieve/default.sieve. The contents of this file are:

require ["fileinto", "mailbox"];
if header :contains "X-Spam-Flag" "YES"
{
	fileinto "Junk";
}

This script will file emails that have been flagged as spam by SpamAssassin into the Junk folder.