Общие и публичные папки в Dovecot: shared mailboxes с ACL для командной работы
Разбираем на инженерном уровне три разных механизма «общего ящика» в Dovecot — public namespace, per-user sharing и псевдо-общие хаки. Как реально резолвится ACL, зачем нужен dict-бэкенд, как отлаживать права через doveadm и почему в кластере тихо разъезжаются подписки.
EvilMail Team12 июля 2026 г.14 мин чтения
Большинство инструкций по «общим ящикам в Dovecot» заканчиваются на строчке mail_shared_explicit_inbox = yes и обещании, что теперь коллеги увидят папку. В реальной команде это не работает по двум причинам, о которых те же инструкции молчат: без dict-бэкенда для хранения ACL расшаренная папка просто не появляется у второго пользователя, а три принципиально разных механизма «общего доступа» валят в одну кучу и настраивают вперемешку. В итоге получаем ящик поддержки, который видят не те люди, подписки, которые разъезжаются между backend-серверами, и dovecot-acl, который «не применяется».
Разложим это так, как нужно инженеру, который уже уронил прод, а не новичку с одной установкой на ноутбуке.
Три разные задачи, которые все называют «общий ящик»
Прежде чем трогать конфиг, определитесь, какую из трёх задач вы решаете. Они несовместимы по архитектуре.
Public namespace — ящик без владельца. support@, info@, sales@
: почта падает в общий Maildir, группа сотрудников читает и отвечает. Владельца-человека нет, квота ни с кого не списывается. Dict
не нужен
— права задаются глобально.
Per-user shared — у папки есть владелец-человек, и он делится ею с коллегой. Алиса расшаривает свою папку Projects Бобу. Здесь обязательны плагин acl, отдельный shared namespace и dict, который хранит запись «кто с кем поделился».
Псевдо-общий через plus-адресацию или Sieve `fileinto` — это НЕ shared mailbox. Вы просто раскладываете входящую почту по папкам одного пользователя. Никакого совместного доступа на уровне IMAP тут нет, права не работают, и путать это с первыми двумя — прямая дорога к «а почему Боб не видит».
Практическое правило: командный ящик без конкретного хозяина — всегда public namespace. Личная папка, которой временно нужно поделиться, — per-user sharing. Если вам «просто разложить письма» — это Sieve, и закройте эту статью.
Как Dovecot резолвит ACL: три слоя
ACL в Dovecot — это плагин acl, и права он собирает из трёх источников в строгом порядке.
1.Global ACL — один файл или каталог, заданный через acl = vfile:/etc/dovecot/global-acl:cache_secs=300. Если это каталог, имя файла внутри должно совпадать с именем mailbox.
2.Per-mailbox `dovecot-acl` — файл прямо в каталоге Maildir конкретной папки. Его правит IMAP-команда SETACL или doveadm acl set.
3.Default rights — то, что осталось владельцу, если ничего явно не задано.
Права записываются буквами, и каждая буква — отдельное разрешение. Запомнить их обязательно, потому что 90% проблем — это именно неверный набор букв.
l — lookup: папка видна в списке. Без l её как будто не существует.
r — read: можно открыть и читать письма.
w — write: менять флаги, кроме \Seen и \Deleted.
s — write-seen: ставить \Seen.
t — write-deleted: ставить \Deleted.
i — insert: класть письма (APPEND, копирование в папку).
p — post: доставка через LDA/LMTP.
e — expunge: физически удалять помеченные письма.
k — create: создавать вложенные папки.
x — delete: удалять саму папку.
a — admin: менять ACL этой папки.
Набор прав можно писать строкой букв (lrwstie), а doveadm acl и IMAP SETACL понимают ещё и длинные имена по одному праву: lookup read write write-seen write-deleted insert post expunge create delete admin. Многобуквенных «групп» вроде read/write в Dovecot нет — только буквы или их полные имена. Идентификаторы, на кого распространяется правило: owner, anyone, authenticated, user=alice, group=support. Негативное правило — ведущий минус: -user=bob wstipe явно отбирает право на запись.
Ключевая ловушка тут одна: l без r. Пользователь видит папку в дереве, но при открытии получает пустоту или ошибку доступа. Это выглядит как «баг Dovecot», а на деле — недописанная буква в global-acl.
Public namespace: ящик поддержки за 20 минут
Общий support@ без владельца — самый частый сценарий и самый простой: dict не нужен, всё держится на global ACL.
Сначала namespace в /etc/dovecot/conf.d/10-mail.conf. LAYOUT=fs кладёт подпапки обычными каталогами (Support, а не .Support), чтобы структура на диске совпадала с тем, что мы создаём руками:
namespace public {
type = public
separator = /
prefix = Public/
location = maildir:/var/mail/public:LAYOUT=fs
subscriptions = no
list = children
}
mail_shared_explicit_inbox = yes
Каталог и права на файловой системе. У нас single-uid модель — весь vmail работает под 5000:5000, поэтому и public Maildir принадлежит ему же:
Теперь сам global ACL. Файл /etc/dovecot/global-acl (или каталог, если разбиваете по mailbox), по одной папке на строку:
Public/Support group=support lrwstipek
Здесь группа support получает полный рабочий набор: видит, читает, ставит флаги, кладёт письма, создаёт подпапки, удаляет письма — но без a (менять ACL) и без x (удалить сам ящик). Группу support Dovecot берёт из userdb: она приходит в поле acl_groups (список через запятую), которое возвращает ваш SQL- или LDAP-userdb.
Доставка входящей почты в public-ящик идёт через Sieve при catch-all на support@:
Или прямым транспортом в Postfix, если LMTP роутит support@domain в этот namespace. После правок — doveconf -n для проверки синтаксиса и doveadm reload. Изменения в global-acl из-за cache_secs=300 подхватятся не мгновенно; для теста кеш можно временно убрать.
Подписки для public по умолчанию выключены (subscriptions = no) — папка видна всем в группе без индивидуальной подписки. Если хотите, чтобы клиент показывал её сразу и держал STATUS быстрым на больших объёмах, включите mailbox_list_index = yes глобально.
Per-user sharing: пользователь делится своей папкой
Здесь появляется dict, и без него ничего не заработает. Алиса хочет расшарить Projects Бобу. Нужен отдельный shared namespace:
namespace {
type = shared
separator = /
prefix = Shared/%%u/
location = maildir:%%h/Maildir:INDEX=%h/shared/%%u
subscriptions = yes
list = children
}
plugin {
acl = vfile
acl_shared_dict = file:/var/lib/dovecot/shared-mailboxes.db
}
%%u в prefix — это владелец расшаренной папки (Боб увидит Shared/alice/Projects), %%h — его домашний каталог. INDEX=%h/... уводит индексы Боба в его собственный каталог, чтобы два пользователя не дрались за один индекс.
acl_shared_dict — сердце механизма. Это реестр «кто кому что расшарил». Файловый бэкенд подходит для одного сервера; в кластере берите dict-прокси поверх общей SQL-базы:
acl_shared_dict = proxy::acl
Без этого dict случается то самое молчаливое зло: Алиса корректно выставила dovecot-acl на своей папке, права правильные, а Боб её всё равно не видит — потому что Dovecot не знает, у кого искать расшаренные папки. Dict и есть тот список, по которому строится namespace для получателя.
Права Алиса выставляет из клиента через IMAP SETACL (RFC 4314). Roundcube умеет это плагином acl, Thunderbird из коробки — нет. Эквивалент на сервере:
Раздел, ради которого статью и стоит сохранить. Когда «доступ не работает», не трогайте конфиг наугад — спросите Dovecot напрямую.
bash
# Что реально видит Боб по этой папке
doveadm acl get -u [email protected] Shared/alice/Projects
# Пошаговый разбор: почему видит/не видит
doveadm acl debug -u [email protected] Shared/alice/Projects
# Есть ли папка вообще в его списке
doveadm mailbox list -u [email protected]
# Пересчитать shared-подписки после ручных правок dict/файлов
doveadm acl recalc -u [email protected]
acl debug — главный инструмент. Он печатает путь к Maildir, проверяет права на файловой системе, читает dict и по шагам объясняет решение. Типичные вердикты из вывода:
Mailbox not found при существующем dovecot-acl — dict пустой, запись alice → bob не создалась. Проверьте acl_shared_dict и что namespace shared реально подключён.
Папка видна, но read недоступен — в правах есть l, но нет r. Дописать в SETACL/global-acl.
Права корректны, но acl debug жалуется на доступ к каталогу — это ФС: Maildir не под 5000:5000 или mode не даёт процессу зайти.
Три источника проблемы — dict, буквы прав, права ФС — и acl debug за один прогон говорит, который из них виноват.
Подводные камни в кластере и на проде
Разъехавшиеся подписки. Несколько backend без общего FS и без dovecot-director — подписки пользователя оказываются на том сервере, куда он попал в этот раз. Нужен либо director с липкими сессиями по пользователю, либо общий backend для подписок и индексов.
Квоты в public. Плагин quota к public namespace не применяется. Доставка в общий ящик не списывается ни с чьей квоты — размер support@ растёт бесконтрольно. Ставьте отдельный скрипт-монитор на du каталога.
Индексы по NFS. Не гоняйте индексы shared-папок по NFS. Уводите их INDEX= на локальный или быстрый путь, иначе получите блокировки и тормоза на STATUS.
uid/gid при shared Maildir. Single-uid модель (всё под 5000) снимает большинство конфликтов доступа. Если у вас per-user uid — shared работать будет через боль, и это отдельный разговор.
Кеш ACL.cache_secs=300 в vfile означает, что правки global-acl видны только через 5 минут. «Изменения не применяются» — почти всегда это. На время отладки уберите cache_secs, потом верните.
`mail_shared_explicit_inbox`. Без него общий INBOX не показывается явно как отдельная папка — включайте, если делитесь именно корневым ящиком, а не подпапками.
Чек-лист внедрения
Плагин acl добавлен в mail_plugins для protocol imap и protocol lmtp.
Для public: namespace type = public, отдельный путь /var/mail/public, dict не нужен.
Для per-user: namespace type = shared с %%u, поднят и доступен acl_shared_dict (file на одиночном сервере, dict-прокси поверх SQL в кластере).
Права ФС на все Maildir и public — 5000:5000, приватные 0700.
Синтаксис global-acl и конфига проверен doveconf -n, буквы прав перечитаны (не забыт r рядом с l).
mail_shared_explicit_inbox = yes, если делитесь корневым INBOX.
mailbox_list_index = yes на больших shared для быстрых STATUS/подписок.
Индексы shared уведены INDEX= с медленного/NFS-хранилища.
Протестирована доставка LMTP/Sieve в общий ящик реальным письмом.
doveadm acl debug по тестовому получателю выдаёт чистый результат.
Записана процедура: как выдать доступ новому сотруднику (добавить в группу или doveadm acl set) и как отозвать.
Держите в голове одно разграничение — public для ящиков без хозяина, per-user с dict для личных папок — и половина проблем с общими ящиками в Dovecot просто не возникает. Оставшуюся половину за минуту вскрывает doveadm acl debug.