Jost-Philip Matysik
2019-May-20 20:05 UTC
enforcing multiple per-mailbox quotas for shared mailboxes
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