Постановка задачи: настроить квоты доменов в Dovecot v.2+ с использованием Quota/Dict с учётом хранения пользователей и квот в MySQL…
Много админов сломало копья, пытаясь настроить эту опцию для Dovecot. Скажем прямо — опция так-себе, но бывает нужна например, чтобы ограничить локальных админов доменов (PostfixAdmin) в создании новых ящиков или в желании получать уведомления при приближении/превышении лимита домена. Да и в бэкэнде PostfixAdmin всё уже есть — грех не использовать!
Кстати, в свое время в безысходность уже упёрлись тут и тут. Ещё масла в огонь подлил "закомментированный кусок" конфигурации в /etc/dovecot/conf.d/90-quota.conf:
root@mail:/# cat /etc/dovecot/conf.d/90-quota.conf # Multiple quota roots are also possible, for example this gives each user # their own 100MB quota and one shared 1GB quota within the domain: plugin { #quota = dict:user::proxy::quota #quota2 = dict:domain:%d:proxy::quota_domain #quota_rule = *:storage=102400 #quota2_rule = *:storage=1048576 }
Ага!!! починили-таки… «Чудно» — воскликнул я и приступил к настройке квот доменов по следующей схеме (ею, кстати, пол-интернета завалено). На примере dovecot-dict-sql.conf.ext создал новый файл dovecot-dict-sql-domain.conf, где хранится информация отображения (маппинга) размера квот домена и ссылается на таблицу домена domain, потом добавил новый словарь и получение квоты из БД mysql…
root@mail:/# vim /etc/dovecot/dovecot-dict-sql-domain.conf connect = host=localhost dbname=postfixadmin user=postfixadmin password=myVerySecurePasswordFromUserPostfixadmin map { pattern = priv/quota/storage table = domain username_field = domain value_field = quota } map { pattern = priv/quota/messages table = quota2 username_field = username value_field = messages } root@mail:/# vim /etc/dovecot/dovecot.conf dict { … sqldomainquota = mysql:/etc/dovecot/dovecot-dict-sql-domain.conf … } root@mail:/# vim /etc/dovecot/conf.d/90-quota.conf plugin { … quota2 = dict:Domain Quota:%d:proxy::sqldomainquota # quota2_rule = } root@mail:/# vim /etc/dovecot/dovecot-sql.conf.ext user_query = SELECT CONCAT('/home/vmail/',domain.domain) as home, \ CONCAT('*:bytes=', IF(mailbox.quota = 0 || mailbox.quota = -1, 0, mailbox.quota)) as quota_rule \ CONCAT('*:bytes=', IF(domain.maxquota = 0 || domain.maxquota = -1, 0, domain.maxquota*1048576)) as quota2_rule \ FROM mailbox, domain \ WHERE username = '%u' AND mailbox.active = '1' AND \ domain.domain = '%d' AND domain.active = '1' #
Рестарт сервиса.
Попытка обновить квоты для пары ящиков домена приводит к интересным результатам — либо в поле quota таблицы domain заносится индивидульное значение последнего "пересчитанного" юзера, либо запись о домене удаляется вовсе. Включив логирование SQL запросов и более внимательно прочитав документацию Quota/Dict прихожу к выводу, что мы не можем использовать таблицу domain, так как типичное обновление (пересчет) квот происходит не через UPDATE, а через DELETE/INSERT:
root@mail:/# doveadm -Dv quota recalc -u admin@example.com root@mail:/# doveadm -Dv quota get -u admin@example.com Quota name Type Value Limit % User Quota STORAGE 6773 8192 82 User Quota MESSAGE 21 - 0 Domain Quota STORAGE 6773 153600 4 Domain Quota MESSAGE 21 - 0 root@mail:/# doveadm -Dv quota recalc -u user1@example.com root@mail:/# doveadm -Dv quota get -u user1@example.com Quota name Type Value Limit % User Quota STORAGE 313 204800 0 User Quota MESSAGE 18 - 0 Domain Quota STORAGE 313 153600 0 Domain Quota MESSAGE 18 - 0 root@mail:/# cat /var/lib/mysql/mail.log | more 1447 Query DELETE FROM domain WHERE domain = 'example.com' 1447 Query DELETE FROM quota2 WHERE username = 'example.com' 1447 Query INSERT INTO domain (quota,domain) VALUES ('6935859','example.com') ON DUPLICATE KEY UPDATE quota='6935859' 1447 Query INSERT INTO quota2 (messages,username) VALUES ('21','example.com') ON DUPLICATE KEY UPDATE messages='21' 1451 Query DELETE FROM domain WHERE domain = 'example.com' 1451 Query DELETE FROM quota2 WHERE username = 'example.com' 1451 Query INSERT INTO domain (quota,domain) VALUES ('321060','example.com') ON DUPLICATE KEY UPDATE bytes='321060' 1451 Query INSERT INTO quota2 (messages,username) VALUES ('18','example.com') ON DUPLICATE KEY UPDATE messages='18' …
что соответственно и «выкашивает» из таблицы domain сам домен example.com, который обслуживается нашим почтовым сервером. Итак — не вариант. Естественно, мы можем «нарисовать» следующий маппинг и подсунуть его Dovecot-у:
root@mail:/# vim /etc/dovecot/dovecot-dict-sql-domain.conf connect = host=localhost dbname=postfixadmin user=postfixadmin password=myVerySecurePasswordFromUserPostfixadmin map { pattern = priv/quota/storage table = quota2 username_field = username value_field = messages } map { pattern = priv/quota/messages table = quota2 username_field = username value_field = messages }
Результат приблизительно тот-же, индивидуальная квота последнего залогиневшегося/пересчитанного юзера попадает в квоту всего домена, что есть баг:
root@mail:/# doveadm -Dv quota recalc -u admin@example.com root@mail:/# doveadm -Dv quota recalc -u user1@example.com root@mail:/# cat /var/lib/mysql/mail.log | more 1462 Query DELETE FROM quota2 WHERE domain = 'example.com' 1462 Query DELETE FROM quota2 WHERE username = 'example.com' 1462 Query INSERT INTO quota2 (bytes,username) VALUES ('6935859','example.com') ON DUPLICATE KEY UPDATE quota='6935859' 1462 Query INSERT INTO quota2 (messages,username) VALUES ('21','example.com') ON DUPLICATE KEY UPDATE messages='21' 1464 Query DELETE FROM quota2 WHERE domain = 'example.com' 1464 Query DELETE FROM quota2 WHERE username = 'example.com' 1464 Query INSERT INTO quota2 (bytes,username) VALUES ('321060','example.com') ON DUPLICATE KEY UPDATE bytes='321060' 1464 Query INSERT INTO quota2 (messages,username) VALUES ('18','example.com') ON DUPLICATE KEY UPDATE messages='18' …
Диагноз — реализация квоты домена в Dovecot кривая. Допиливать и решать через cron, doveadm+grep+sed сил и желания уже нет. По итогу, в случае с PostfixAdmin 2.3+, Dovecot 2.1+ и виртуальными пользователями в MySQL — воз и ныне там.
У Вас есть элегантное или не очень решение — рад буду выслушать!
Комментарии 1
- 1
Дмитрий Владимирович — Jul 23, 2014 at 04:41 PM
решения так и не нашлось(