Extended Brain Storage

OpenBSD: Dovecot with Sieve, ManageSieve and Quota

Posted on September 23, 2016

The installation and configuration of Sieve, ManageSieve and Quota support in Dovecot in OpenBSD...


Sieve is a scripting language for filtering e-mail messages. It was originally developed by the CMU Cyrus Project and is currently specified in RFC5228. The Sieve filtering rules can be created by a either a graphical user interface (GUI)-based editor or using a text editor. The process of transferring the scripts to the (mail) server depends on its software.

Therefore, ManageSieve protocol was defined in RFC5804 in order to allow users to securely manage their script rules on a remote server (and also alert them to syntactically flawed scripts), where they are usually stored in a .sieve file in user's home directory.

ManageSieve in Dovecot

A minimal configuration is already present in Dovecot's configuration. However, the sieve_deprecated section can be commented as follows:

$ vi /etc/dovecot/conf.d/20-managesieve.conf
protocols = $protocols sieve
service managesieve-login {
  inet_listener sieve {
    port = 4190
  #inet_listener sieve_deprecated {
  #  port = 2000
protocol sieve {

Sieve in Dovecot

Sieve support in Dovecot is not installed by default. The Pigeonhole project provides Sieve support as a plugin to Dovecot's LDA and can be installed as follows:

$ pkg_add dovecot-pigeonhole
quirks-2.367 signed on 2017-10-03T11:21:28Z
dovecot-pigeonhole-0.4.20v0: ok

Note: Nowadays it is recommended to use LMTP instead of LDA. The main difference is that the LDA is a short-running process, started as a binary from command line, while LMTP is a long-running process started by Dovecot's master process.

There are two Dovecot's plugins implementing the Sieve processes:

All user defined Sieve scripts, that are managed by ManageSieve, will be stored in the virtual user's home directory, i.e. in:

$ cd /var/vmail/domain.tld/USERNAME/sieve

Just one sieve script can be active per user and is automatically sym-linked to:

$ ls -lA /var/vmail/domain.tld/USERNAME/.dovecot.sieve

ManageSieve ensures that the existing .dovecot.sieve file does not get overwritten. After a new sieve script is activated, the old one is backed up and moved to the sieve sub-folder.

External programs can be also used to filter or pipe (process) messages using executable scripts.

As previously noted, the LMTP is preferred and thus, enabled by default. It can be verified by:

$ grep ^protocols /etc/dovecot/dovecot.conf
protocols = imap lmtp

The Sieve plugin needs to be enabled as follows:

$ vi /etc/dovecot/conf.d/20-lmtp.conf
protocol lmtp {
  mail_plugins = $mail_plugins sieve

And the IMAPSieve plugin as follows:

$ vi /etc/dovecot/conf.d/20-imap.conf
protocol imap {
  mail_plugins = $mail_plugins imap_sieve

The default configuration of the Sieve plugin is sufficient and looks as follows:

$ grep -vE "^[ \t]*$|^.*#" /etc/dovecot/conf.d/90-sieve.conf
plugin {
  sieve = file:~/sieve;active=~/.dovecot.sieve

However, the IMAPSieve plugin needs a bit of polishing (can be applied as is):

$ vi /etc/dovecot/conf.d/90-sieve.conf
plugin {
  # Location of scripts which are uploaded through ManageSieve
  sieve = file:~/sieve;active=~/.dovecot.sieve

  # Sieve plugin

  # Global sieve scripts to run before and after processing ALL incoming mail
  sieve_before = /etc/dovecot/sieve-before.d
  sieve_after  = /etc/dovecot/sieve-after.d

  # Maximum size of a single sieve script per user
  sieve_quota_max_storage = 50M

  # IMAP Sieve plugin
  sieve_plugins = sieve_imapsieve sieve_extprograms

  # From elsewhere to Junk folder
  imapsieve_mailbox1_name = Junk
  imapsieve_mailbox1_causes = COPY
  imapsieve_mailbox1_before = file:/etc/dovecot/sieve/report-spam.sieve

  # From Junk folder to elsewhere
  imapsieve_mailbox2_name = *
  imapsieve_mailbox2_from = Junk
  imapsieve_mailbox2_causes = COPY
  imapsieve_mailbox2_before = file:/etc/dovecot/sieve/report-ham.sieve

  sieve_global = /etc/dovecot/sieve
  sieve_pipe_bin_dir = /etc/dovecot/sieve
  sieve_global_extensions = +vnd.dovecot.pipe

Dovecot's configuration can be verified by:

$ doveconf -n | head -n 1
# 2.2.32 (dfbe293d4): /etc/dovecot/dovecot.conf

and the new settings traditionally applied by:

$ rcctl restart dovecot

Sieve can be configured to automatically move e-mails considered as "spam" (or "ham") using the IMAPSieve plugin.

First, a Sieve directory needs to be created:

$ mkdir -p /etc/dovecot/sieve/

"Spam"-considered messages will be reported using the following Sieve script:

$ vi /etc/dovecot/sieve/report-spam.sieve
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];

if environment :matches "imap.email" "*" {
  set "email" "${1}";

pipe :copy "train-spam.sh" [ "${email}" ];

"Ham"-considered messages will be reported using the following Sieve script:

$ vi /etc/dovecot/sieve/report-ham.sieve
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];

if environment :matches "imap.mailbox" "*" {
  set "mailbox" "${1}";

if string "${mailbox}" "Trash" {

if environment :matches "imap.email" "*" {
  set "email" "${1}";

pipe :copy "train-ham.sh" [ "${email}" ];

Since Dovecot doesn't have write permission to the directory, the scripts needs to be compiled manually using the Pigeonhole's sievec script compiler as follows:

$ cd /etc/dovecot/sieve/
$ sievec report-spam.sieve
$ sievec report-ham.sieve

The previously defined Sieve scripts execute appropriate shell scripts. Considering that the e-mail content filter is performed by Rspamd and ClamAV, they can be created as follows:

$ echo "exec /usr/local/bin/rspamc -h localhost:11332 learn_spam" > /etc/dovecot/sieve/train-spam.sh
$ echo "exec /usr/local/bin/rspamc -h localhost:11332 learn_ham" > /etc/dovecot/sieve/train-ham.sh
$ chmod +x /etc/dovecot/sieve/train-{spam,ham}.sh

Beside users' scripts, Sieve is able to utilise globally defined scripts. For this purpose, the following directories need to be created:

$ mkdir -p /etc/dovecot/{sieve-before.d,sieve-after.d}

In order to make Dovecot to automatically move e-mails tagged as spam by the Rspamd to the users' Junk folder (based on the X-Spam-Status header), the the following global filter needs to be defined:

$ vi /etc/dovecot/sieve-before.d/10-rspamd.sieve
require ["fileinto"];

if header :is "X-Spam-Status" "Yes" {
  fileinto "Junk";

Again, this script needs to be manually compiled:

$ cd /etc/dovecot/sieve-before.d
$ sievec 10-rspamd.sieve

Finally, the new settings can be applied by:

$ rcctl reload dovecot

Dovecot is now configured to retrain the Rspamd's spam filter according the Junk folder operation. In other words, when users put messages into (or out of) the Junk folder, the rspamc command action is triggered and logged into:

$ tail -f /var/log/rspamd/rspamd.log

The Sieve interface should be accessible via the TCP port 4190 and its availability can be verified using the telnet command as follows (STARTTLS will be used if available, the session needs to be ended by <Ctrl>+] sequence):

$ telnet server.domain.tld 4190
Trying EGRESS_IP...
Connected to server.domain.tld.
Escape character is '^]'.
"IMPLEMENTATION" "Dovecot Pigeonhole"
"SIEVE" "fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext imapsieve vnd.dovecot.imapsieve"
"NOTIFY" "mailto"
"VERSION" "1.0"
OK "Dovecot ready."
telnet> quit
Connection closed.

Sieve Usage in Thunderbird

Mozilla Thunderbird (TB) does not come with Sieve support. Thomas Schmid created the Sieve extension. Once downloaded, it needs to be installed manually via TB's menu:

Tools -> Add-ons -> "cog icon" on top -> Install Add-on From File...

The extension needs to be activated on a selected e-mail account as follows:

The usage is straightforward via TB's menu: Tools -> Sieve Message Filters.

Considering (an example) that it is required to:

a new Sieve script can be created as follows:

require ["copy", "fileinto", "variables"];
if address :is "to" "rua-dmarc@domain.tld" {
  fileinto "INBOX.rua-dmarc";
} elsif address :is "to" "postmaster@domain.tld" {
  fileinto "INBOX.postmaster";
} elsif address :domain :is "from" "filtered-domain.tld" {
  # forward message to another mailbox and keep the message in INBOX
  redirect :copy "forwarding-mailbox@forwarding-domain.tld";
} else {
  # the rest goes into INBOX ("implicit keep" by default, but this is explicit)

In order to activate the previously created Sieve script, the following action needs to be performed:

Verification can be done on the server by checking that the automatic symbolic link (symlink) appeared in the virtual user's home directory:

$ ls -lA /var/vmail/domain.tld/USERNAME/ | grep .dovecot.sieve
lrwx------   1 vmail  vmail     19 Sep 23 12:34 .dovecot.sieve -> sieve/aliases.sieve

Note: Since the developer has not published the extension officialy in Mozilla Add-ons, it needs to be updated manually.

Vacation aka Out-of-Office

Due to the Pigeonhole Sieve, the vacation extension is available by default, but can be verified as follows:

$ doveconf -a | grep vacation
managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext imapsieve vnd.dovecot.imapsieve

In order to configure the extension, the original Sieve script needs to be updated with the following:

require ["variables", "vacation"];
# store old Subject line so it can be used in vacation message
if header :matches "Subject" "*" {
  set "subjwas" ": ${1}";
  :days 1
  :subject "Out of office reply${subjwas}"
  :addresses ["j.doe@domain.tld", "john.doe@domain.tld"]
"Dear Sender,
I'm out of office, please contact Joan Doe instead.
Best regards
John Doe";

The default behaviour is sending one reply per day and in order to disable the extension, the previously defined rules need to be removed or commented.

Quotas in Dovecot

Quota root is a concept from IMAP4 QUOTA extension, which was defined in RFC2087. In theory, there could exist different quota roots, e.g. "user quota" and "domain quota" roots.

The quota plugin is installed in Dovecot by default, and it can be enabled as follows:

$ vi /etc/dovecot/conf.d/10-mail.conf
mail_plugins = $mail_plugins quota

The quota_imap plugin can be enabled as follows:

$ vi /etc/dovecot/conf.d/20-imap.conf
protocol imap {
  mail_plugins = $mail_plugins imap_quota
  # or
  mail_plugins = $mail_plugins imap_sieve imap_quota

Considering that Dovecot is set up according to OpenBSD: Dovecot, the quota limits are defined within the mailQuota attribute in the ou=people,dc=domain,dc=tld subtree. In order to load this parameter by Dovecot, the LDAP search string (user_attrs) needs to be updated as follows:

$ vi /etc/dovecot/dovecot-ldap.conf.ext
hosts = server.domain.tld
tls = yes
auth_bind = yes
ldap_version = 3
dn = uid=dovecot,ou=services,dc=domain,dc=tld
base = ou=people,dc=domain,dc=tld
user_filter = (&(objectClass=PostfixBookMailAccount)(uid=%n))
user_attrs = uid=user,mailStorageDirectory=home=/var/vmail/%$,mailQuota=quota_rule=*:bytes=%$
pass_filter = (&(objectClass=PostfixBookMailAccount)(uid=%n))
pass_attrs = uid=user
# http://wiki.dovecot.org/Authentication/PasswordSchemes
#default_pass_scheme = CRYPT
default_pass_scheme = SSHA   # used to store passwords in LDAP

Let's assume the following example:

In order to implement the previous example, the following needs to be done:

$ vi /etc/dovecot/conf.d/90-quota.conf
plugin {
  quota = maildir:User quota
  quota_rule = *:storage=256M
  quota_rule2 = Trash:storage=+100M
  quota_rule3 = SPAM:ignore

Alternatively: Instead of hard size over quota limit, percents can be used. Percents are relative to the default rule and the "%" character needs to be written twice to escape it. An example configuration of an additional 10% space can be found as follows:

$ vi /etc/dovecot/conf.d/90-quota.conf
plugin {
  quota = maildir:User quota
  quota_rule = *:storage=256MB
  # 10% of quota
  quota_rule2 = Trash:storage=+10%%
  quota_rule3 = Spam:ignore

Quota warnings can be configured in order to inform users that they exceeded a specific limit. Also, a "reverse warning", which starts with "-" character, can be configured when a quota drops below a specific value (e.g. user no longer over quota):

$ vi /etc/dovecot/conf.d/90-quota.conf
plugin {
  quota_warning = storage=95%% quota-warning 95 %u
  quota_warning2 = storage=80%% quota-warning 80 %u
  quota_warning3 = -storage=100%% quota-warning below 100 %u # user is no longer over quota
service quota-warning {
  executable = script /usr/local/bin/quota-warning.sh
  user = vmail # an unprivileged user to execute the quota warnings
  unix_listener quota-warning {
   user = vmail
   mode = 0666

The previous configuration defines the /usr/local/bin/quota-warning.sh script within the executable parameter. This shell script needs to be created manually and it can look like the following:

$ vi /usr/local/bin/quota-warning.sh
cat << EOF | /usr/local/libexec/dovecot/dovecot-lda -d $USER -o "plugin/quota=maildir:User quota:noenforcing"
From: postmaster@domain.tld
Subject: Quota Warning

Your mailbox is now $PERCENT% full.

The script needs to be executable, hence:

$ chmod +x /usr/local/bin/quota-warning.sh

And finally, Dovecot requires a restart to apply the quota settings:

$ rcctl restart dovecot

Verification of the quota setup can be performed by checking the current quota of a particular user using the doveadm command as follows:

# doveadm -f tab quota get -u user1@domain.tld
Quota name      Type    Value   Limit   %
User quota      STORAGE 51      524287  0
User quota      MESSAGE 42      -       0

Should it be necessary, the quota limit can be manually recalculated for a particular user:

$ doveadm quota recalc -u user1@domain.tld

Tags: #OpenBSD #security #Dovecot #Sieve #ManageSieve #quota

⏴ Previous Post Next Post ⏵