Zimbra подставляет NULL вместо корпоративной подписи: как проверить дисклеймер каждого домена
Если в исходящем письме вместо корпоративной подписи получатель видит слово «(null)», проблема почти всегда не в шаблоне, а в том, что дисклеймер включён глобально, а текст для конкретного домена не задан. Разбираю, как Zimbra ищет подпись для каждого домена, почему один и тот же переключатель ломает часть писем и что делать, чтобы новый домен не портил репутацию всей почтовой системы.
Почему вообще появляется «(null)» вместо подписи
Дисклеймер в Zimbra — это не MIME-заголовок и не шаблон в веб-клиенте, а текст, который подставляет утилита altermime на этапе обработки почты в amavis, уже после того как письмо прошло антиспам и антивирус. Когда я настраиваю корпоративную почту клиентам с несколькими доменами на одном сервере, отдельные дисклеймеры на домен — обычная задача: у филиала должна быть своя подпись, у управляющей компании — своя, и подменять чужой текст нельзя.
Поддержка дисклеймеров на уровне отдельного домена появилась в ZCS 8.5 и работает через глобальный переключатель zimbraDomainMandatoryMailSignatureEnabled: пока он выключен, доменные дисклеймеры не добавляются вообще; как только его включают через zmprov mcf zimbraDomainMandatoryMailSignatureEnabled TRUE, Zimbra начинает искать текст и HTML-версию подписи отдельно для каждого домена, у которого есть исходящая почта через этот MTA.
И вот здесь кроется ловушка: включение атрибута глобальное. Оно затрагивает сразу все домены на сервере, а не только тот, для которого администратор реально прописал текст. Статья базы знаний Zimbra (KB 23305) формулирует это прямо: при zimbraDomainMandatoryMailSignatureEnabled=TRUE дисклеймер включается для всех доменов, и там, где zimbraAmavisDomainDisclaimerText и zimbraAmavisDomainDisclaimerHTML не настроены, в конец письма добавляется NULL (в клиентах это часто выглядит как «(null)»). Связанный баг — 101069 в Bugzilla Zimbra. Молча пропустить ненастроенный домен механизм не умеет.
Отдельно стоит развести два разных механизма, которые в разговоре администраторы часто путают. Личная подпись пользователя (zimbraPrefMailSignature, настраивается в веб-клиенте на вкладке «Настройки → Подпись») живёт в почтовом ящике и добавляется клиентом при написании письма. Дисклеймер домена — это принудительная вставка на уровне MTA, которая срабатывает для всех писем домена независимо от того, что сотрудник написал в личных настройках, и её нельзя отключить из интерфейса обычного пользователя. Когда клиент жалуется «у меня в подписи задвоение» или «подпись не моя», первым делом я уточняю, о каком именно механизме речь — иначе легко потратить час на правку личной подписи, когда проблема в домене.
Как правильно включить дисклеймер на домен
Порядок действий короткий, но каждый шаг важен именно в этой последовательности. Сначала глобально разрешаем механизм подомeнных дисклеймеров, затем задаём текст и HTML-версию для конкретного домена, и только потом просим MTA перечитать файлы. Пропуск последнего шага — частая причина, почему администратор меняет текст в LDAP, а получатели всё ещё видят старую подпись или тот же «(null)».
zmprov mcf zimbraDomainMandatoryMailSignatureEnabled TRUE
zmprov md teatralnaya-gavan.example zimbraAmavisDomainDisclaimerText 'ООО «Театральная гавань» | +7 (000) 000-00-00 | teatralnaya-gavan.example'
zmprov md teatralnaya-gavan.example zimbraAmavisDomainDisclaimerHTML '<p style="font-size:11px;color:#666">ООО «Театральная гавань»</p>'После того как текст и HTML записаны в LDAP, на первом MTA от пользователя zimbra нужно выполнить /opt/zimbra/libexec/zmaltermimeconfig -e teatralnaya-gavan.example. Я заглядывал в исходник скрипта: ключ -e помечает домен в LDAP атрибутом amavisDisclaimerOptions, после чего скрипт генерирует в /opt/zimbra/data/altermime/ файлы .txt, .html и .b64 для всех отмеченных доменов — именно их altermime реально подставляет в письмо. На остальных MTA, если их несколько, тот же скрипт запускают без параметров: он заново генерирует файлы на своём узле из уже записанных в LDAP настроек. Чтобы не ждать, пока amavis подхватит изменения, KB советует zmamavisdctl restart. Отключить дисклеймер конкретного домена — zmaltermimeconfig -d домен на первом MTA и снова запуск без параметров на остальных. Если забыть шаг на втором MTA, письма, ушедшие через него, продолжат показывать старую подпись или тот же NULL.
Почему у одних доменов всё нормально, а у новых — NULL
Типичная картина: на сервере пять доменов, у трёх подпись настраивали ещё на этапе внедрения, а два добавили позже — под новый филиал или отдельный бренд. Глобальный флаг zimbraDomainMandatoryMailSignatureEnabled уже включён, значит altermime обязан подставить дисклеймер в письма всех пяти доменов. Для трёх доменов файлы есть, для двух — нет, и получатели новых доменов регулярно видят «(null)» в подписи, а не отсутствие дисклеймера вообще.
Логика связана с тем, как zmaltermimeconfig раскладывает файлы: файлы .txt, .html и .b64 создаются только для доменов, которые хоть раз включили через zmaltermimeconfig -e. Домены, добавленные позже и не прошедшие этот шаг, файлов не имеют, а глобальная функция для них всё равно активна — в итоге в тело письма попадает NULL. Поэтому «подпись настроили в LDAP» и «подпись реально работает» — два разных состояния.
Отдельно стоит смотреть на несовпадение между тем, что администратор изменил в консоли, и тем, что реально хранится в LDAP. zmprov gd имя.домена zimbraAmavisDomainDisclaimerText zimbraAmavisDomainDisclaimerHTML быстро показывает, заполнены ли атрибуты у конкретного домена, а ls -l /opt/zimbra/data/altermime/ на каждом MTA — есть ли для него файлы. Я всегда сверяю оба вывода перед тем, как что-то менять: пустые атрибуты при отсутствии файлов — это и есть классическая картина NULL.
Ситуация усложняется, когда домены добавляли разные администраторы в разное время — обычная история для компании, которая росла через присоединение филиалов или смену подрядчика по IT. У меня были случаи, когда на одном сервере часть доменов настраивали ещё на ZCS 8.6, часть — уже на Zimbra 8.8.15, и практика заполнения атрибутов у разных людей отличалась: кто-то задавал только текстовую версию, кто-то — только HTML, а у части доменов не было заполнено вообще ничего. При включённом глобальном флаге результат один — «(null)» у всех, кому чего-то не хватает, вне зависимости от того, когда и кем домен добавлялся.
Как закрыть NULL для доменов без собственной подписи
Не у каждого домена на сервере должна быть содержательная подпись — иногда это технический домен для рассылок, где никакой дисклеймер вообще не нужен. Здесь неправильно выключать глобальный флаг: он потянет за собой домены, для которых дисклеймер настроен и обязателен. Решение точечное: для домена, которому подпись не нужна, задают пустые значения атрибутов, а не оставляют их незаполненными.
zmprov md tech.teatralnaya-gavan.example zimbraAmavisDomainDisclaimerText ''
zmprov md tech.teatralnaya-gavan.example zimbraAmavisDomainDisclaimerHTML ''
/opt/zimbra/libexec/zmaltermimeconfig -e tech.teatralnaya-gavan.exampleПосле такой записи и запуска с -e zmaltermimeconfig создаёт в /opt/zimbra/data/altermime/ три пустых файла — .b64, .html и .txt — для этого домена; ровно этот обход и описан в KB 23305. Пустые строки в zmprov здесь нужны лишь как гарантия, что в LDAP не осталось старого текста: в Zimbra присвоение пустой строки фактически очищает атрибут. Решает дело именно регистрация домена ключом -e и появление пустых файлов — после этого письмо уходит без дисклеймера, как и было задумано. Путают здесь постоянно: правят атрибуты, но забывают про zmaltermimeconfig, и NULL остаётся.
Кейс: театральная школа «Театральная гавань», 24 рабочих места
Условный клиент — частная театральная школа «Театральная гавань» на 24 рабочих места: административный офис, приёмная комиссия и отдельный поддомен для рассылок расписания родителям. Почта на Zimbra уже стояла несколько лет, обслуживала один основной домен, дисклеймер был настроен ещё на внедрении и работал без вопросов. Когда школа завела отдельный поддомен для системы уведомлений о расписании, администратор просто добавил домен в Zimbra и прописал маршрутизацию, не трогая настройки дисклеймера — логично решив, что раз общая функция уже работает, новый домен подхватит те же правила.
Первые письма из новой системы уведомлений родители стали получать с текстом вида «Здравствуйте, завтра репетиция в 16:00. (null)» — и часть родителей восприняла это как признак спам-рассылки или взлома почты. Жалобы дошли до администрации раньше, чем до IT: несколько человек просто отписались от уведомлений. Диагностика заняла меньше часа: zmprov gd notify.teatralnaya-gavan.example zimbraAmavisDomainDisclaimerText zimbraAmavisDomainDisclaimerHTML показал, что оба атрибута у нового домена пусты, в /opt/zimbra/data/altermime/ файлов для него нет, а zimbraDomainMandatoryMailSignatureEnabled на сервере включён глобально ещё с внедрения основного домена.
Решили, что рассылке расписания собственная подпись не нужна — это не деловая переписка, а технические уведомления с уже понятным получателю адресом отправителя. Задали пустые значения обоих атрибутов домена уведомлений, прогнали zmaltermimeconfig -e notify.teatralnaya-gavan.example, проверили тестовым письмом на внешний ящик. «(null)» пропал в течение нескольких минут, никаких изменений в дисклеймере основного домена школы не потребовалось. Отдельно завели чек-лист: при добавлении нового домена в Zimbra явным пунктом стоит «проверить zimbraAmavisDomainDisclaimerText/HTML», а не полагаться на то, что общая функция сама разберётся.
Как проверять дисклеймеры на кластере из нескольких MTA
На сервере с одним MTA всё просто: одна команда zmaltermimeconfig -e domain — и файлы на месте. На кластере из двух и более MTA легко получить ситуацию, когда дисклеймер починили только на одном узле, а почта части клиентов или сотрудников уходит через второй, где старые файлы altermime так и остались. Я всегда проверяю содержимое /opt/zimbra/data/altermime/ на каждом MTA после изменения, а не только на том, где выполнялась команда с -e.
На дополнительных MTA после первого домена достаточно запустить /opt/zimbra/libexec/zmaltermimeconfig без параметров — он заново генерирует файлы на конкретном узле из LDAP для всех доменов, включённых через -e, не требуя повторно перечислять каждый домен вручную. Это удобно при регулярном обслуживании, но именно поэтому пропущенный на одном узле шаг остаётся незамеченным неделями: письма продолжают уходить с правильной подписью через один MTA и с «(null)» или старым текстом через другой, и жалобы выглядят непоследовательными, пока не проверишь маршрут конкретного письма.
Отдельно стоит держать в голове, что часть форумных обсуждений (включая старые темы про disclaimer show null на версии 8.5) описывает похожий симптом с другой первопричиной — например, ошибкой с amavisDisclaimerOptions из бага 95237, обсуждавшейся для ZCS 8.5.0 FOSS Patch 1. Прежде чем чинить по аналогии с чужим форумным тредом, я всегда сверяю именно свой вывод zmprov gd и содержимое каталога altermime на своём сервере: одинаковый текст ошибки в вебе не гарантирует одинаковую причину.
Ещё один практический момент — резервное копирование каталога altermime перед массовыми правками. На сервере с десятком доменов и историей ручных правок разных администраторов в /opt/zimbra/data/altermime/ реально накапливается путаница: файлы для уже удалённых доменов, файлы с именами, оставшимися от переименования домена, дублирующиеся .html без соответствующего .txt. Перед тем как переписывать дисклеймеры массово, я делаю tar czf altermime-backup-$(date +%F).tar.gz /opt/zimbra/data/altermime/ и только потом прохожусь по каждому домену из zmprov gad командой zmprov gd с проверкой обоих атрибутов — так видно фактическое состояние, а не то, что должно быть по документации. Саму сверку дисклеймеров по всем доменам я вписал в регламент ежемесячной проверки Zimbra — пять минут раз в месяц дешевле, чем объяснять клиентам, откуда в письме NULL.
Частые вопросы
Можно ли просто выключить zimbraDomainMandatoryMailSignatureEnabled, чтобы убрать NULL?
Можно, но это отключит дисклеймеры сразу для всех доменов сервера, включая те, где подпись обязательна по внутренним правилам компании. Для домена без собственной подписи правильнее задать пустые значения zimbraAmavisDomainDisclaimerText и zimbraAmavisDomainDisclaimerHTML, а не трогать глобальный флаг.
Почему после изменения текста дисклеймера в консоли получатели всё ещё видят старую подпись?
Чаще всего забыли выполнить zmaltermimeconfig после правки zmprov md — именно этот скрипт пересобирает файлы в /opt/zimbra/data/altermime/, которые реально читает altermime. Изменение в LDAP само по себе файлы не обновляет.
Нужно ли запускать zmaltermimeconfig -e на каждом MTA кластера?
Нет. Команду с -e и именем домена выполняют один раз на первом MTA: она помечает домен в LDAP и генерирует файлы. На остальных MTA достаточно запустить zmaltermimeconfig без параметров — скрипт сам соберёт файлы для всех включённых доменов на своём узле.
Как быстро проверить, заполнены ли атрибуты дисклеймера у домена?
Командой zmprov gd имя.домена zimbraAmavisDomainDisclaimerText zimbraAmavisDomainDisclaimerHTML, а затем ls -l /opt/zimbra/data/altermime/ на каждом MTA. Пустые атрибуты без файлов домена в каталоге — типичная причина NULL.
Дисклеймер добавляется до или после проверки на спам и вирусы?
Текст вставляет altermime, который вызывает amavis при обработке исходящей почты, то есть на уровне MTA, а не в веб-клиенте. На решения антиспама и антивируса о самом письме дисклеймер не влияет, поэтому искать причину NULL в фильтрах бессмысленно.
Источники
- Zimbra Tech Center — Steps to configure per domain disclaimer on ZCS 8.5 and higher versions — KB 23284: zmprov mcf zimbraDomainMandatoryMailSignatureEnabled TRUE, атрибуты zimbraAmavisDomainDisclaimerText/HTML на домен, zmaltermimeconfig -e DOMAIN на первом MTA и без параметров на остальных, zmamavisdctl restart, отключение через -d. https://wiki.zimbra.com/wiki/Steps_to_configure_per_domain_disclaimer_on_ZCS_8.5_and_higher_versions
- Zimbra Tech Center — Steps to fix NULL disclaimer on outgoing emails — KB 23305: при глобальном TRUE домены без zimbraAmavisDomainDisclaimerText/HTML получают NULL; обход — пустые значения атрибутов и zmaltermimeconfig -e, создающий пустые .b64/.html/.txt в /opt/zimbra/data/altermime/; связанный bug 101069. https://wiki.zimbra.com/wiki/Steps_to_fix_NULL_disclaimer_on_outgoing_emails
- Zimbra Forums — Diclaimer shown (null) — Обсуждение симптома «(null)» вместо подписи у администраторов ZCS и обмен рабочими обходами. https://forums.zimbra.org/viewtopic.php?t=47756
- Zimbra на GitHub — исходник zmaltermimeconfig (zm-core-utils) — Ключи -e/-d, запись amavisDisclaimerOptions в домен, генерация .txt/.html/.b64 только при глобальном TRUE и только для включённых доменов. https://github.com/Zimbra/zm-core-utils/blob/develop/src/libexec/zmaltermimeconfig



