enforcing multiple per-mailbox quotas for shared mailboxes

Jost-Philip Matysik jost-philip at matysik.it
Mon May 20 23:05:41 EEST 2019


Hi!

I'm trying to get quotas for shared mailboxes set up on my server. It's
not working, and I fail to understand why. Documentation for setup of
this complexity is rather scarce on the web, and the discussions I found
either don't directly apply or terminate with "I got it working" but
no explanation.
Can someone please help?
The setup is rather lengthy and complicated, so I'll try to give a
summary first for easier understanding.
All help is appreciated! Please respond if you need more info!

Thanks!
Best regards,
Jost


Basic setup (where am I coming from?):
======================================
- This is a Debian 9 system with a more recent version of dovecot
  installed from https://repo.dovecot.org/ce-2.3-latest/debian/stretch
- users have individual mailboxes with individual quotas  -- this works great!
- users can share mailboxes with other users through ACLs. To achieve
  this, there exists a wildcarded namespace with
  prefix="ZZZ_Freigaben/%%u" --this also works great!
==> up to here it's pretty much following the examples from the Wiki to
the letter.


Configuration goal (what do I want to achieve?):
================================================
- attach quotas to mailboxes, not logins
- when copying/moving mails across shared mailboxes during imap
  sessions, enforce quota based on target mailbox, not logged-in user
  doing the copying.
To clarify:
==> so if Bob has a quota of 500MB, all messages in Bob's mailbox
should count against (and only against!) Bob's quota, regardless of who
put them there.
==> if Alice has access to her own mailbox (directly), as well as Bob's
mailbox and Dave's mailbox (through sharing), she should see 3
individual quotas when logging in: her own quota (200MB) for everything
in her mailbox, bob's quota (300MB) for everything in Bob's mailbox, and
Dave's quota (10GB) for everything in Dave's mailbox. These 3 quotas
should be completely independant and neither block, nor override each
other.


Setup Idea (how I tried to get there):
======================================
- "quota=..." "quota_rule" and "quota_rule2" always refer to the user's
  own mailbox (with an additional rule for Trash). Everyone has those,
  so these are loaded statically from dovecot config file.
- "quota_rule" is overwritten from userdb with the user's individual
  mailbox quota (the more I like you, the more space you get...)
- since the number of different additional quotas required per user
  depends on how many mailboxes are shared with that user, individual
  "quota2=...", "quota3=...", "quotaN=..." fields are dynamically
  generated by the MySQL backend and loaded from userdb upon login.
- consequently, for each "quotaN=..." entry, a corresponding
  "quotaN_rule=*:storage=XXX" is generated and returned from userdb
  (substitute N=1,2,3,4,... accordingly)


Observations:
=============
1. enforcing quota for the user's personal mailbox works as expected,
   both through IMAP and when delivering incoming mail
2. overriding "quota_rule=..." from userdb for the user's personal
   mailbox works great. Individual quota is recognized and enforced both
   through IMAP and when delivering incoming mail.
3. dynamically loading "quota2=...", "quota3=..." etc. from userdb
   doesn't seem to work at all! I can see them being added as extra
   fields in the logs upon user login, but the quota-plugin seems to
   completely ignore them. They are not enforces, and tools like
   'doveadm quota' list the userdb fields in the debug messages, but do
   not interpret them in any way. They do not throw errors either, the
   additional quota roots are just silently ignored.
4. the end result is that in an IMAP session the logged-in user's quota
   is enforced for their individual mailbox, but as soon as they write
   to someone else's mailbox (move a mail
   to /ZZZFreigaben/bob/Some-Subfolder), no quota is enforced at all!

Additional debugging done:
==========================
if I hardcode a "quota2=" and/or "quota3=" in the config file, I can
observe the following:
- If I hardcode "quota2=count:some_name:ns=ZZZ_Freigaben/", dovecot and
  doveadm will recognize the additional quota root. But since the folder
  "ZZZ_Freigaben/" on its own isn't a mailbox (it's just a path
  CONTAINING mailboxes), the quota is neither displayed in clients, nor
  enforced. It has no real-word effect, other than 'doveadm quota'
  showing an additional line.
- If I hardcode "quota2=count:other_name:ns=ZZZ_Freigaben/postmaster/"
  in the config file (with that path being the real IMAP path to a
  shared mailbox), dovecot will complain in the log saying

Error: quota: Unknown namespace: ZZZ_Freigaben/postmaster/

dovecot will start and work for the most part, but again completely
ignore settings for the additional quota.

- additionally, trying to override "quota2_rule=..." from userdb doesn't
  work as it does with "quota_rule=..."! If I have both
  "quota_rule=..." and "quota2_rule=..." in the config file as well as
  returned from userdb (with different values), dovecot will use
  "quota_rule" from userdb, but "quota2_rule" from config file. This
  behavior seems inconsistent!
- I tried both maildir and count backends (seperately or mixed). Both
  show the same behavior.
- I tried appending or removing an empty "ns=" to the first "quota=..."
  entry (the wiki shows some examples with it, some without). Makes no
  difference...



Configuration Dumps below (slightly redacted to remove complete email
addresses or IPs)
==========================


output of dovecot -n:
# 2.3.6 (7eab80676): /etc/dovecot/dovecot.conf
# Pigeonhole version 0.5.6 (92dc263a)
# OS: Linux 4.9.0-9-amd64 x86_64 Debian 9.9 
# Hostname: [censored]
auth_failure_delay = 10 secs
auth_mechanisms = plain login
default_vsz_limit = 512 M
dict {
  acl = mysql:/etc/dovecot/dovecot-dict-sql.conf.ext
}
listen = *
lmtp_rcpt_check_quota = yes
login_greeting = Mail system ready.
mail_gid = vmail
mail_location = maildir:~/Maildir
mail_plugins = " quota acl"
mail_uid = vmail
managesieve_notify_capability = mailto
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
namespace Freigaben {
  list = children
  location = maildir:%%h/Maildir:INDEXPVT=%h/Maildir/Freigaben/%%u
  prefix = ZZZ_Freigaben/%%u/
  separator = /
  subscriptions = no
  type = shared
}
namespace inbox {
  inbox = yes
  location = 
  mailbox Drafts {
    auto = subscribe
    special_use = \Drafts
  }
  mailbox Junk {
    auto = subscribe
    autoexpunge = 61 days
    special_use = \Junk
  }
  mailbox Sent {
    auto = subscribe
    special_use = \Sent
  }
  mailbox "Sent Messages" {
    special_use = \Sent
  }
  mailbox Trash {
    auto = subscribe
    autoexpunge = 61 days
    special_use = \Trash
  }
  prefix = 
  separator = /
  subscriptions = yes
  type = private
}
passdb {
  args = /etc/dovecot/dovecot-sql.conf.username
  driver = sql
}
plugin {
  acl = vfile:/etc/dovecot/dovecot-acl
  acl_defaults_from_inbox = yes
  acl_shared_dict = proxy::acl
  imapsieve_mailbox1_before = file:/etc/dovecot/sieve/learn-spam.sieve
  imapsieve_mailbox1_causes = COPY
  imapsieve_mailbox1_name = Junk
  imapsieve_mailbox2_before = file:/etc/dovecot/sieve/learn-ham.sieve
  imapsieve_mailbox2_causes = COPY
  imapsieve_mailbox2_from = Junk
  imapsieve_mailbox2_name = *
  quota = count:User quota:ns=
  quota_grace = 1%%
  quota_rule = *:storage=5G
  quota_rule2 = Trash:storage=+100M
  quota_status_nouser = DUNNO
  quota_status_overquota = 552 5.2.2 Mailbox is full
  quota_status_success = DUNNO
  quota_vsizes = yes
  sieve = file:~/sieve;active=~/.dovecot.sieve
  sieve_after = /etc/dovecot/sieve-after
  sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
  sieve_max_script_size = 10M
  sieve_pipe_bin_dir = /etc/dovecot/sieve
  sieve_plugins = sieve_imapsieve sieve_extprograms
  sieve_quota_max_storage = 50M
}
postmaster_address = postmaster at matysik-ingenieurwesen.de
protocols = " imap lmtp sieve"
service auth-worker {
  user = vmail
}
service auth {
  unix_listener /var/spool/postfix/private/auth {
    group = postfix
    mode = 0660
    user = postfix
  }
  unix_listener auth-userdb {
    mode = 0666
  }
}
service dict {
  unix_listener dict {
    group = vmail
    mode = 0660
    user = dovecot
  }
}
service imap-login {
  inet_listener imap {
    port = 0
  }
  inet_listener imaps {
    address = [censored]
    port = 993
    ssl = yes
  }
}
service imap {
  process_limit = 1024
}
service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    group = postfix
    mode = 0600
    user = postfix
  }
}
service managesieve-login {
  inet_listener sieve {
    address = [censored]
    port = 4190
  }
  service_count = 1
}
service managesieve {
  process_limit = 64
}
ssl_cert =
</etc/letsencrypt/live/fullchain.pem
ssl_cipher_list =
ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
ssl_dh = # hidden, use -P to show it
ssl_key = # hidden, use -P to show it
ssl_min_protocol = TLSv1.2
ssl_options = no_compression
ssl_prefer_server_ciphers = yes

# comment: since imap logins differ from email-addresses I have 2
# userdb queries. One will return fields for %u='login', the other will
# return fields for %u='email at domain.tld'.
# no %u will ever make BOTH userdbs return rows (they are mutually
exclusive)
userdb {
  args = /etc/dovecot/dovecot-sql.conf.email
  driver = sql
}
userdb {
  args = /etc/dovecot/dovecot-sql.conf.username
  driver = sql
}
protocol lmtp {
  mail_plugins = " quota acl sieve"
}
protocol imap {
  mail_plugins = " quota acl imap_sieve imap_quota imap_acl"
}


example userdb output:
=======================
root at server:/# doveadm -Dv user jost
Debug: Loading modules from directory: /usr/lib/dovecot/modules
Debug: Module loaded: /usr/lib/dovecot/modules/lib01_acl_plugin.so
Debug: Module loaded: /usr/lib/dovecot/modules/lib10_quota_plugin.so
Debug: Loading modules from directory: /usr/lib/dovecot/modules/doveadm
Debug: Module loaded: /usr/lib/dovecot/modules/doveadm/lib10_doveadm_acl_plugin.so
Debug: Skipping module doveadm_expire_plugin, because dlopen() failed: /usr/lib/dovecot/modules/doveadm/lib10_doveadm_expire_plugin.so: undefined symbol: expire_set_deinit (this is usually intentional, so just ignore this message)
Debug: Module loaded: /usr/lib/dovecot/modules/doveadm/lib10_doveadm_quota_plugin.so
Debug: Module loaded: /usr/lib/dovecot/modules/doveadm/lib10_doveadm_sieve_plugin.so
Debug: Skipping module doveadm_fts_lucene_plugin, because dlopen() failed: /usr/lib/dovecot/modules/doveadm/lib20_doveadm_fts_lucene_plugin.so: undefined symbol: lucene_index_iter_deinit (this is usually intentional, so just ignore this message)
Debug: Skipping module doveadm_fts_plugin, because dlopen() failed: /usr/lib/dovecot/modules/doveadm/lib20_doveadm_fts_plugin.so: undefined symbol: fts_user_get_language_list (this is usually intentional, so just ignore this message)
Debug: Skipping module doveadm_mail_crypt_plugin, because dlopen() failed: /usr/lib/dovecot/modules/doveadm/libdoveadm_mail_crypt_plugin.so: undefined symbol: mail_crypt_box_get_pvt_digests (this is usually intentional, so just ignore this message)
field	valuedoveadm(jost)<25290><>: Debug: auth USER input: jost home=/var/vmail/matysik.it/jost-philip uid=5000 gid=5000 acl_groups=matysik,admin quota_rule=*:storage=10G quota_rule2 = Trash:storage=+100M quota2 = count:shared quota for maschinen:ns=ZZZ_Freigaben/maschinen/ quota2_rule = *:storage=500M quota3 = count:shared quota for matysik:ns=ZZZ_Freigaben/matysik/ quota3_rule = *:storage=10G quota4 = count:shared quota for postmaster:ns=ZZZ_Freigaben/postmaster/ quota4_rule = *:storage=1G

uid	5000
gid	5000
home	/var/vmail/matysik.it/jost-philip
mail	maildir:~/Maildir
acl_groups	matysik,admin
quota_rule	*:storage=10G
quota_rule2 	 Trash:storage=+100M
quota2 	 count:shared quota for maschinen:ns=ZZZ_Freigaben/maschinen/
quota2_rule 	 *:storage=500M
quota3 	 count:shared quota for matysik:ns=ZZZ_Freigaben/matysik/
quota3_rule 	 *:storage=10G
quota4 	 count:shared quota for postmaster:ns=ZZZ_Freigaben/postmaster/
quota4_rule 	 *:storage=1G


More information about the dovecot mailing list